Beautiful Failures at 33rd Degree
33rd in Kraków, is rolling baby.Tomorrow, together with Maciek Próchniak, we are giving a talk about failures. There is a problem with failures withing our Read more
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>3.1.0.RELEASE</version> </dependency>
<servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/feed</url-pattern> </servlet-mapping>Notice, I set the url-pattern to “/feed” which means I don't want Spring MVC to handle any other urls in my app (I'm using a different web framework for the rest of the app). I also give it a brand new contextConfigLocation, where only the mvc configuration is kept.
<dependency> <groupId>net.java.dev.rome</groupId> <artifactId>rome</artifactId> <version>1.0.0</version> </dependency>
@Controller public class FeedController { static final String LAST_UPDATE_VIEW_KEY = "lastUpdate"; static final String NEWS_VIEW_KEY = "news"; private NewsRepository newsRepository; private String viewName; protected FeedController() {} //required by cglib public FeedController(NewsRepository newsRepository, String viewName) { notNull(newsRepository); hasText(viewName); this.newsRepository = newsRepository; this.viewName = viewName; } @RequestMapping(value = "/feed", method = RequestMethod.GET) @Transactional public ModelAndView feed() { ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName(viewName); List<News> news = newsRepository.fetchPublished(); modelAndView.addObject(NEWS_VIEW_KEY, news); modelAndView.addObject(LAST_UPDATE_VIEW_KEY, getCreationDateOfTheLast(news)); return modelAndView; } private Date getCreationDateOfTheLast(List<News> news) { if(news.size() > 0) { return news.get(0).getCreationDate(); } return new Date(0); } }And here's a test for it, in case you want to copy&paste (who doesn't?):
@RunWith(MockitoJUnitRunner.class) public class FeedControllerShould { @Mock private NewsRepository newsRepository; private Date FORMER_ENTRY_CREATION_DATE = new Date(1); private Date LATTER_ENTRY_CREATION_DATE = new Date(2); private ArrayList<News> newsList; private FeedController feedController; @Before public void prepareNewsList() { News news1 = new News().title("title1").creationDate(FORMER_ENTRY_CREATION_DATE); News news2 = new News().title("title2").creationDate(LATTER_ENTRY_CREATION_DATE); newsList = newArrayList(news2, news1); } @Before public void prepareFeedController() { feedController = new FeedController(newsRepository, "viewName"); } @Test public void returnViewWithNews() { //given given(newsRepository.fetchPublished()).willReturn(newsList); //when ModelAndView modelAndView = feedController.feed(); //then assertThat(modelAndView.getModel()) .includes(entry(FeedController.NEWS_VIEW_KEY, newsList)); } @Test public void returnViewWithLastUpdateTime() { //given given(newsRepository.fetchPublished()).willReturn(newsList); //when ModelAndView modelAndView = feedController.feed(); //then assertThat(modelAndView.getModel()) .includes(entry(FeedController.LAST_UPDATE_VIEW_KEY, LATTER_ENTRY_CREATION_DATE)); } @Test public void returnTheBeginningOfTimeAsLastUpdateInViewWhenListIsEmpty() { //given given(newsRepository.fetchPublished()).willReturn(new ArrayList<News>()); //when ModelAndView modelAndView = feedController.feed(); //then assertThat(modelAndView.getModel()) .includes(entry(FeedController.LAST_UPDATE_VIEW_KEY, new Date(0))); } }Notice: here, I'm using fest-assert and mockito. The dependencies are:
<dependency> <groupId>org.easytesting</groupId> <artifactId>fest-assert</artifactId> <version>1.4</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>1.8.5</version> <scope>test</scope> </dependency>
import org.springframework.web.servlet.view.feed.AbstractAtomFeedView; [...] public class AtomFeedView extends AbstractAtomFeedView { private String feedId = "tag:yourFantastiSiteName"; private String title = "yourFantastiSiteName: news"; private String newsAbsoluteUrl = "http://yourfanstasticsiteUrl.com/news/"; @Override protected void buildFeedMetadata(Map<String, Object> model, Feed feed, HttpServletRequest request) { feed.setId(feedId); feed.setTitle(title); setUpdatedIfNeeded(model, feed); } private void setUpdatedIfNeeded(Map<String, Object> model, Feed feed) { @SuppressWarnings("unchecked") Date lastUpdate = (Date)model.get(FeedController.LAST_UPDATE_VIEW_KEY); if (feed.getUpdated() == null || lastUpdate != null || lastUpdate.compareTo(feed.getUpdated()) > 0) { feed.setUpdated(lastUpdate); } } @Override protected List<Entry> buildFeedEntries(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { @SuppressWarnings("unchecked") List<News> newsList = (List<News>)model.get(FeedController.NEWS_VIEW_KEY); List<Entry> entries = new ArrayList<Entry>(); for (News news : newsList) { addEntry(entries, news); } return entries; } private void addEntry(List<Entry> entries, News news) { Entry entry = new Entry(); entry.setId(feedId + ", " + news.getId()); entry.setTitle(news.getTitle()); entry.setUpdated(news.getCreationDate()); entry = setSummary(news, entry); entry = setLink(news, entry); entries.add(entry); } private Entry setSummary(News news, Entry entry) { Content summary = new Content(); summary.setValue(news.getShortDescription()); entry.setSummary(summary); return entry; } private Entry setLink(News news, Entry entry) { Link link = new Link(); link.setType("text/html"); link.setHref(newsAbsoluteUrl + news.getId()); //because I have a different controller to show news at http://yourfanstasticsiteUrl.com/news/ID entry.setAlternateLinks(newArrayList(link)); return entry; } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"> <property name="mediaTypes"> <map> <entry key="atom" value="application/atom+xml"/> <entry key="html" value="text/html"/> </map> </property> <property name="viewResolvers"> <list> <bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/> </list> </property> </bean> <bean class="eu.margiel.pages.confitura.feed.FeedController"> <constructor-arg index="0" ref="newsRepository"/> <constructor-arg index="1" value="atomFeedView"/> </bean> <bean id="atomFeedView" class="eu.margiel.pages.confitura.feed.AtomFeedView"/> </beans>
@Test public void savingAndUpdatingPersonShouldCreateTwoHistoricalVersions() { //given Person person = createAndSavePerson(); String oldFirstName = person.getFirstName(); String newFirstName = oldFirstName + "NEW"; //when updatePersonWithNewName(person, newFirstName); //then verifyTwoHistoricalVersionsWereSaved(oldFirstName, newFirstName); } private Person createAndSavePerson() { Transaction transaction = session.beginTransaction(); Person person = PersonFactory.createPerson(); session.save(person); transaction.commit(); return person; } private void updatePersonWithNewName(Person person, String newName) { Transaction transaction = session.beginTransaction(); person.setFirstName(newName); session.update(person); transaction.commit(); } private void verifyTwoHistoricalVersionsWereSaved(String oldFirstName, String newFirstName) { List<Object[]> personRevisions = getPersonRevisions(); assertEquals(2, personRevisions.size()); assertEquals(oldFirstName, ((Person)personRevisions.get(0)[0]).getFirstName()); assertEquals(newFirstName, ((Person)personRevisions.get(1)[0]).getFirstName()); } private List<Object[]> getPersonRevisions() { Transaction transaction = session.beginTransaction(); AuditReader auditReader = AuditReaderFactory.get(session); List<Object[]> personRevisions = auditReader.createQuery() .forRevisionsOfEntity(Person.class, false, true) .getResultList(); transaction.commit(); return personRevisions; }
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" > ... <property name="eventListeners"> <map key-type="java.lang.String" value-type="org.hibernate.event.EventListeners"> <entry key="post-insert" value-ref="auditEventListener"/> <entry key="post-update" value-ref="auditEventListener"/> <entry key="post-delete" value-ref="auditEventListener"/> <entry key="pre-collection-update" value-ref="auditEventListener"/> <entry key="pre-collection-remove" value-ref="auditEventListener"/> <entry key="post-collection-recreate" value-ref="auditEventListener"/> </map> </property> </bean> <bean id="auditEventListener" class="org.hibernate.envers.event.AuditEventListener" />
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name="dataSource" ref="dataSource"/> </bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager" > <property name="sessionFactory" ref="sessionFactory"/> </bean>