Testing
using-lishys-notes.notes

Guide


The guide pane on the right contains information organized into sections.

Use the and buttons to move through the sections, or click an accordion tab (shown below) to go directly to a particular topic.



Code Reference


Each paragraph in the guide is usually associated with a particular piece of code. An orange arrow indicates that the code displayed in the middle pane is relevant to the guide text.



If the arrow is gray, simply click anywhere on the paragraph and the relevant code will be displayed.



Code Highlighter


The highlight feature identifies areas of the code which relate to a word or term in the guide. Hover the mouse over any underlined word and the relevant code will be highlighted.



You can disable the highlighting feature at any time by clicking on the button.

Don't forget to make the appropriate code page visible by clicking on any paragraph with a gray arrow.
HibernatePersonDao.java
package com.lishman.testing.data.hibernate;

import java.util.Collection;

import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.lishman.testing.data.PersonDao;
import com.lishman.testing.domain.Person;

@Repository
@Transactional
public class HibernatePersonDao implements PersonDao {

    @Autowired
    SessionFactory sessionFactory;
    
    @SuppressWarnings("unchecked")
    @Transactional(readOnly = true)
    public Collection<Person> getAll() {
        return sessionFactory
                    .getCurrentSession()
                    .getNamedQuery("Person.findAll")
                    .list();
    }

    @Transactional(readOnly = true)
    public Person getById(int personId) {
        return (Person) sessionFactory
                    .getCurrentSession()
                    .get(Person.class, personId);
        
    }
    
    @Transactional(readOnly = true)
    public Person getByName(String personName) {
        return (Person) sessionFactory
                    .getCurrentSession()
                    .getNamedQuery("Person.findByName")
                    .setParameter("person_name", personName)
                    .uniqueResult();
    }

    public Person save(Person person) {
        Person savedPerson = (Person) sessionFactory.getCurrentSession().merge(person);
        sessionFactory.getCurrentSession().flush();
        return savedPerson;
        
    }

    public void delete(Person person) {
        sessionFactory.getCurrentSession().delete(person);
        sessionFactory.getCurrentSession().flush();
    }


}
JpaPersonDao.java
package com.lishman.testing.data.jpa;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.jpa.JpaTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.lishman.testing.data.PersonDao;
import com.lishman.testing.domain.Person;

@Repository
@Transactional
public class JpaPersonDao implements PersonDao {

    @Autowired
    JpaTemplate jpaTemplate;
    
    @SuppressWarnings("unchecked")
    @Transactional(readOnly = true)
    public Collection<Person> getAll() {
        return jpaTemplate.findByNamedQuery("Person.findAll"); 
    }

    @Transactional(readOnly = true)
    public Person getById(int personId) {
        return (Person) jpaTemplate.find(Person.class, personId);
    }

    @SuppressWarnings("unchecked")
    @Transactional(readOnly = true)
    public Person getByName(String name) {
        Map<String, String> params = Collections.singletonMap("person_name", name);
        List<Person> persons = jpaTemplate.findByNamedQueryAndNamedParams("Person.findByName", params);
        return persons.isEmpty() ? null : persons.get(0);
    }

    public Person save(Person person) {
        Person savedPerson = jpaTemplate.merge(person);
        jpaTemplate.flush();
        return savedPerson;
    }

    public void delete(Person person) {
        jpaTemplate.remove(getById(person.getId()));
        jpaTemplate.flush();
    }

}
PersonDao.java
package com.lishman.testing.data;

import java.util.Collection;

import com.lishman.testing.domain.Person;

public interface PersonDao {

    public Collection<Person> getAll();

    public Person getById(int personId);

    public Person getByName(String personName);

    public Person save(Person person);

    public void delete(Person person);

}
Person.java
package com.lishman.testing.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;

import org.hibernate.validator.constraints.NotEmpty;
import org.hibernate.validator.constraints.Range;

@Entity
@Table(name = "PERSON")
@NamedQueries ({
    @NamedQuery(name="Person.findAll",     query="select p from Person p order by p.name"),
    @NamedQuery(name="Person.findByName",  query="select p from Person p where p.name = :person_name")
})
public class Person {

    @Id
    @SequenceGenerator(name = "PersonSequence", sequenceName = "PERSON_SEQ")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "PersonSequence")
    @Column(name = "PRSN_ID")
    private Integer id;

    @Column(name = "PRSN_NAME")
    @NotEmpty
    private String name;

    @Column(name = "PRSN_AGE")
    @NotNull
    @Range(min=10, max=100)
    private Integer age;
    
    public Person () {}
    
    public Person (String name, Integer age) {
        setName(name);
        setAge(age);
    }
    
    public Person (int id, String name, Integer age) {
        this(name, age);
        setId(id);
    }

    public boolean isNew() {
        return id == null;
    }

    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    
}

PersonValidator.java
package com.lishman.testing.domain;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;

import com.lishman.testing.data.PersonDao;

@Component
public class PersonValidator {
    
    @Autowired
    private PersonDao personDao;

    public void validate(Person person, Errors errors) {

        if (!errors.hasFieldErrors("name")) {
            Person existingPerson = personDao.getByName(person.getName());
            if (existingPerson != null
                 && (person.isNew() || 
                     !person.getId().equals(existingPerson.getId()))) {
                errors.rejectValue("name", "validation.exists", "already exists");
            }
        }
    }

}

MyService.java
package com.lishman.testing.service;

import java.util.Collection;

import com.lishman.testing.domain.Person;


public interface MyService {
    
    public Person pickWinner();
    
    public Collection<Person> getAllPersons();

    public Person getPersonById(int personId);

    public Person getPersonByName(String name);

    public Person savePerson(Person person);

    public void deletePerson(Person person);
    
}

MyServiceImpl.java
package com.lishman.testing.service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.lishman.testing.data.PersonDao;
import com.lishman.testing.domain.Person;

@Service
public class MyServiceImpl implements MyService {
    
    @Autowired
    private PersonDao personDao;
    
    @Override
    public Person pickWinner() {
        List<Person> persons = new ArrayList<Person>(personDao.getAll());
        if (persons.size() == 0) {
            return null;
        }
        int randomInt = new Random().nextInt(persons.size()) ;
        Person winner = persons.get(randomInt);
        personDao.delete(winner);
        return winner;
    }
    
    @Override
    public Collection<Person> getAllPersons() {
        return personDao.getAll();
    }

    @Override
    public Person getPersonById(int personId) {
        return personDao.getById(personId);
    }

    @Override
    public Person getPersonByName(String name) {
        return personDao.getByName(name);
    }

    @Override
    public Person savePerson(Person person) {
        return personDao.save(person); 
    }
    
    @Override
    public void deletePerson(Person person) {
        personDao.delete(person); 
    }
}
PersonController.java
package com.lishman.testing.web;

import java.util.Collection;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import com.lishman.testing.domain.Person;
import com.lishman.testing.service.MyService;

@Controller
public class PersonController {

    @Autowired
    private MyService myService;

    @RequestMapping("/person-list")
    @ModelAttribute("persons")
    public Collection<Person> getPersonList(HttpServletRequest request) {
        request.setAttribute("my_attribute", "my_value");
        return myService.getAllPersons();
    }

    @RequestMapping("/person-details/{id}")
    public ModelAndView getPerson(@PathVariable("id") int personId) {
        Person person = myService.getPersonById(personId);
        return new ModelAndView("person-details", "person", person);
                                
    }
}
PersonForm.java
package com.lishman.testing.web;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;

import com.lishman.testing.domain.Person;
import com.lishman.testing.domain.PersonValidator;
import com.lishman.testing.service.MyService;

@Controller
@RequestMapping("/person-form")
@SessionAttributes("person")
public class PersonForm {

    @Autowired
    private PersonValidator personValidator;

    @Autowired
    private MyService myService;

    @RequestMapping(method = RequestMethod.GET)
    public Person setUpForm(@RequestParam(value = "id", required = false) Integer id) {
        return id == null ? new Person() : myService.getPersonById(id);
    }

    @RequestMapping(params = "save", method = RequestMethod.POST)
    public String save(@Valid Person person, 
                       BindingResult result,
                       SessionStatus status) {

        personValidator.validate(person, result);
        if (result.hasErrors()) {
            return "person-form";
        } else {
            myService.savePerson(person);
            status.setComplete();
            return "redirect:person-list";
        }
    }
    
    @RequestMapping(params = "delete", method = RequestMethod.POST)
    public String delete(Model model, SessionStatus status) {
        Person person = (Person) model.asMap().get("person");
        myService.deletePerson(person);
        status.setComplete();
        return "redirect:person-list";
    }
        
}

jpa-persistence.xml
<?xml version="1.0" encoding="UTF-8"?>

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
             version="1.0">
             
  <persistence-unit name="testing" transaction-type="RESOURCE_LOCAL"/>
  
</persistence>
HibernatePersonDaoTest-context.xml
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

  <context:component-scan base-package="com.lishman.testing.data.hibernate"/>

  <bean id="sessionFactory"
        class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
        p:dataSource-ref="dataSource"
        p:configurationClass="org.hibernate.cfg.AnnotationConfiguration"
        p:packagesToScan="com.lishman.testing.domain">
    <property name="hibernateProperties">
      <props>
        <prop key="hibernate.dialect">${hibernate.dialect}</prop>
        <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
        <prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
        <prop key="hibernate.generate_statistics">${hibernate.generate_statistics}</prop>
      </props>
    </property>
    <property name="eventListeners">
      <map>
        <entry key="merge">
          <bean class="org.springframework.orm.hibernate3.support.IdTransferringMergeEventListener"/>
        </entry>
      </map>
    </property>
  </bean>

  <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager"
        p:sessionFactory-ref="sessionFactory"/>
        
</beans>
HibernatePersonDaoTest.java
package com.lishman.testing.data.hibernate;

import org.springframework.test.context.ContextConfiguration;

import com.lishman.testing.data.PersonDaoTest;

@ContextConfiguration
public class HibernatePersonDaoTest extends PersonDaoTest {

}
JpaPersonDaoTest-context.xml
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

  <context:component-scan base-package="com.lishman.testing.data.jpa"/>

  <bean id="entityManagerFactory"
        class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
        p:persistenceUnitName="testing"
        p:dataSource-ref="dataSource"
        p:persistenceXmlLocation="classpath:META-INF/jpa-persistence.xml">
    <property name="jpaVendorAdapter">
      <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
            p:databasePlatform="${hibernate.dialect}"
            p:showSql="${hibernate.show_sql}"/>
    </property>
  </bean>

  <bean id="transactionManager"
        class="org.springframework.orm.jpa.JpaTransactionManager"
        p:entityManagerFactory-ref="entityManagerFactory"/>

  <bean id="jpaTemplate"
        class="org.springframework.orm.jpa.JpaTemplate"
        p:entityManagerFactory-ref="entityManagerFactory"/>
        
</beans>
JpaPersonDaoTest.java
package com.lishman.testing.data.jpa;

import org.springframework.test.context.ContextConfiguration;

import com.lishman.testing.data.PersonDaoTest;

@ContextConfiguration
public class JpaPersonDaoTest extends PersonDaoTest {

}
PersonDaoStub.java
package com.lishman.testing.data.stub;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.springframework.stereotype.Component;

import com.lishman.testing.data.PersonDao;
import com.lishman.testing.domain.Person;

@Component
public class PersonDaoStub implements PersonDao {
    
    private Map<Integer, Person> persons = new HashMap<Integer, Person>();
    private int lastId;
    
    public PersonDaoStub() {
        persons.put(1, new Person(1, "First Person",   10));
        persons.put(2, new Person(2, "Second Person",  20));
        persons.put(3, new Person(3, "Third Person",   30));
        persons.put(4, new Person(4, "Fourth Person",  40));
        lastId = 4;
    }

    @Override
    public Collection<Person> getAll() {
        return persons.values(); 
    }

    @Override
    public Person getById(int personId) {
        return getPerson(personId);
    }

    @Override
    public Person getByName(String name) {
        for (Person person : persons.values()) {
            if (person.getName().equals(name)) {
                return getPerson(person.getId());
            }
        }
        return null;
    }

    @Override
    public Person save(Person person) {
        if (person.isNew()) {
            person.setId(++lastId);
        }
        persons.put(person.getId(), person);
        return person;
    }

    @Override
    public void delete(Person person) {
        persons.remove(person.getId());
    }
    
    public Map<Integer, Person> getPersonMap() {
        return Collections.unmodifiableMap(persons);
    }
    
    public int getLastId() {
        return lastId;
    }
    
    private Person getPerson(int personId) {
        if (!persons.containsKey(personId)) {
            return null;
        }        
        Person p = persons.get(personId);
        return new Person(p.getId(), p.getName(), p.getAge());
    }

}
PersonDaoTest-context.xml
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">

  <context:property-placeholder location="classpath:jdbc.properties"/>

  <bean id="dataSource"
        class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close"
        p:driverClassName="${jdbc.driverClassName}"
        p:url="${jdbc.url}"
        p:username="${jdbc.username}"
        p:password="${jdbc.password}"/>

</beans>
PersonDaoTest.java
package com.lishman.testing.data;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;

import com.lishman.testing.data.PersonDao;
import com.lishman.testing.domain.Person;

@ContextConfiguration
public class PersonDaoTest extends AbstractTransactionalJUnit4SpringContextTests {

    @Autowired
    private PersonDao personDao;
    
    @Test
    public void correctNumberOfRowsAreReturned() {        
        assertEquals(super.countRowsInTable("PERSON"), personDao.getAll().size());
    }
    
    @Test
    public void correctPersonIsReturnedForSpecifiedId() {
        Person person = personDao.getById(1);
        assertEquals(new Integer(1), person.getId());
        assertEquals("First Person", person.getName());
        assertEquals((Integer) 10,   person.getAge());
    }
    
    @Test
    public void correctPersonIsReturnedForSpecifiedName() {
        Person person = personDao.getByName("Second Person");
        assertEquals(new Integer(2),  person.getId());
        assertEquals("Second Person", person.getName());
        assertEquals((Integer) 20,    person.getAge());
    }
    
    @Test
    public void personIsCreated() {
        Person person = personDao.save(new Person("New Name", 50));

        Integer currId = super.simpleJdbcTemplate.queryForInt(
                "SELECT person_seq.currval FROM dual");
        currId *= 50; // generator has default allocationSize of 50
        assertEquals(currId, person.getId());
        
        Person newPerson = getPersonFromDatabase(currId);
        assertEquals(currId,        newPerson.getId());
        assertEquals("New Name",    newPerson.getName());
        assertEquals((Integer) 50,  newPerson.getAge());
    }

    @Test
    public void personIsUpdated() {
        Person person = personDao.getById(4);
        assertEquals((Integer) 4,    person.getId());
        assertEquals("Fourth Person", person.getName());
        assertEquals((Integer) 40,   person.getAge());

        person.setName("Updated Name");
        person.setAge(60);
        
        personDao.save(person);
        
        Person newPerson = getPersonFromDatabase(4);
        assertEquals((Integer) 4,    newPerson.getId());
        assertEquals("Updated Name", newPerson.getName());
        assertEquals((Integer) 60,   newPerson.getAge());
    }
    
    @Test
    public void personIsDeleted() {
        Person person = personDao.getById(3);
        assertNotNull(person);
        
        personDao.delete(person);
        
        int rowCount = super.simpleJdbcTemplate.queryForInt(
                "SELECT count(*) FROM person WHERE prsn_id = 3");
        assertEquals(0, rowCount);
    }

    private Person getPersonFromDatabase(int currId) {
        return super.simpleJdbcTemplate.queryForObject(
                "SELECT prsn_id AS id, prsn_name AS name, prsn_age AS age " +
                "FROM person " +
                "WHERE prsn_id = ?",
                new BeanPropertyRowMapper<Person>(Person.class), 
                currId);
    }
}
DomainTest-context.xml
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
      
  <mvc:annotation-driven/>
   
  <context:component-scan base-package="com.lishman.testing.domain"/> 
  <context:component-scan base-package="com.lishman.testing.data.stub"/>

</beans>
PersonTest.java
package com.lishman.testing.domain;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import java.util.Iterator;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validator;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("DomainTest-context.xml")
public class PersonTest {

    @Autowired
    Validator validator;
       
    @Test
    public void personWithNullNameIsRejected() {
        Person person = new Person(null, 10);
        
        Set<ConstraintViolation<Person>> violations = validator.validate(person);
        
        assertEquals(1, violations.size());
        ConstraintViolation<Person> violation = violations.iterator().next();
        assertEquals("name", violation.getPropertyPath().toString());
        assertEquals("may not be empty", violation.getMessage().toString());   
    }
    
    @Test
    public void personWithNoNameIsRejected() {
        Person person = new Person("", 10);
        
        Set<ConstraintViolation<Person>> violations = validator.validate(person);
        
        assertEquals(1, violations.size());
        ConstraintViolation<Person> violation = violations.iterator().next();
        assertEquals("name", violation.getPropertyPath().toString());
        assertEquals("may not be empty", violation.getMessage().toString());   
    }
    
    @Test
    public void personWithNullAgeIsRejected() {
        Person person = new Person("Name", null);
        
        Set<ConstraintViolation<Person>> violations = validator.validate(person);
        
        assertEquals(1, violations.size());
        ConstraintViolation<Person> violation = violations.iterator().next();
        assertEquals("age", violation.getPropertyPath().toString());
        assertEquals("may not be null", violation.getMessage().toString());   
    }
    
    @Test
    public void personWithNoNameAndNoAgeIsRejected() {
        Person person = new Person();
        
        Set<ConstraintViolation<Person>> violations = validator.validate(person);
        
        assertEquals(2, violations.size());
        Iterator<ConstraintViolation<Person>> iter = violations.iterator();
        String firstMessage = iter.next().getMessage().toString();
        String secondMessage = iter.next().getMessage().toString();
        
        if (firstMessage.equals("may not be null")) {
            assertEquals("may not be empty", secondMessage);
        } else if (firstMessage.equals("may not be empty")) {
            assertEquals("may not be null", secondMessage);
        } else {
            fail("invalid first message");
        }
    }
    
    @Test
    public void personWhoIsTooYoungIsRejected() {
        Person person = new Person("Name", 9);
        
        Set<ConstraintViolation<Person>> violations = validator.validate(person);
        
        assertEquals(1, violations.size());
        ConstraintViolation<Person> violation = violations.iterator().next();
        assertEquals("age", violation.getPropertyPath().toString());
        assertEquals("must be between 10 and 100", violation.getMessage().toString());   
    }
    
    @Test
    public void personWhoIsTooOldIsRejected() {
        Person person = new Person("Name", 101);
        
        Set<ConstraintViolation<Person>> violations = validator.validate(person);
        
        assertEquals(1, violations.size());
        ConstraintViolation<Person> violation = violations.iterator().next();
        assertEquals("age", violation.getPropertyPath().toString());
        assertEquals("must be between 10 and 100", violation.getMessage().toString());   
    }

}

PersonValidatorTest.java
package com.lishman.testing.domain;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("DomainTest-context.xml")
public class PersonValidatorTest {

    @Autowired
    PersonValidator personValidator;
       
    @Test
    public void newPersonWithUniqueNameIsCreated() {
        Person person = new Person("Unique Name", 80);
        BindingResult result = new BeanPropertyBindingResult(person, "person");
        
        personValidator.validate(person, result);

        assertEquals(0, result.getAllErrors().size());
        assertFalse(result.hasFieldErrors("name"));        
    }
    
    @Test
    public void newPersonWithDuplicateNameIsRejected() {
        Person person = new Person("First Person", 25);
        BindingResult result = new BeanPropertyBindingResult(person, "person");
        
        personValidator.validate(person, result);

        assertEquals(1, result.getAllErrors().size());
        assertTrue(result.hasFieldErrors("name"));
        assertEquals("already exists", result.getFieldError("name").getDefaultMessage());
    }
    
    @Test
    public void existingPersonWithUniqueNameIsCreated() {
        Person person = new Person(1, "Unique Name", 25);
        BindingResult result = new BeanPropertyBindingResult(person, "person");
        
        personValidator.validate(person, result);

        assertEquals(0, result.getAllErrors().size());
        assertFalse(result.hasFieldErrors("name"));        
    }
    
    @Test
    public void existingPersonWithDuplicateNameIsRejected() {
        Person person = new Person(1, "Second Person", 25);
        BindingResult result = new BeanPropertyBindingResult(person, "person");
        
        personValidator.validate(person, result);

        assertEquals(1, result.getAllErrors().size());
        assertTrue(result.hasFieldErrors("name"));
        assertEquals("already exists", result.getFieldError("name").getDefaultMessage());
 
    }

}

MyServiceTest.java
package com.lishman.testing.service;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;

import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.annotation.Repeat;
import org.springframework.test.annotation.Timed;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.util.ReflectionTestUtils;

import com.lishman.testing.data.stub.PersonDaoStub;
import com.lishman.testing.domain.Person;

@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({})
public class MyServiceTest {
    
    private static MyService myStaticService;
    private static PersonDaoStub personStaticDaoStub;

    private MyService myService;
    private PersonDaoStub personDaoStub;
    
    @BeforeClass
    public static void initStatic() {
        personStaticDaoStub = new PersonDaoStub();
        myStaticService = new MyServiceImpl();
        ReflectionTestUtils.setField(myStaticService, "personDao", personStaticDaoStub);
    }
    
    @Before
    public void init() {
        personDaoStub = new PersonDaoStub();
        myService = new MyServiceImpl();
        ReflectionTestUtils.setField(myService, "personDao", personDaoStub);
    }
    
    @Test
    @Repeat(4)
    @Timed(millis=1000)
    public void allWinnersArePickedWithinOneSecond() {
        Person person = myStaticService.pickWinner();
        assertNotNull(person);
    }
    
    @Test
    public void allPersonsObjectsAreReturned() {        
        assertEquals(4, myService.getAllPersons().size());
    }
    
    @Test
    public void personForSpecifiedIdIsReturned() {
        Person person = myService.getPersonById(3);
        assertEquals(new Integer(3), person.getId());
        assertEquals("Third Person", person.getName());
        assertEquals((Integer) 30,   person.getAge());
    }
    
    @Test
    public void personForSpecifiedNameIsReturned() {
        Person person = myService.getPersonByName("Fourth Person");
        assertEquals(new Integer(4),  person.getId());
        assertEquals("Fourth Person", person.getName());
        assertEquals((Integer) 40,    person.getAge());
    }
    
    @Test
    public void personIsCreated() {
        Person person = myService.savePerson(new Person("New Name", 60));
        int id = person.getId();
        assertEquals(new Integer(id), person.getId());
        assertEquals("New Name",      person.getName());
        assertEquals((Integer) 60,    person.getAge());
        
        Person newPerson = personDaoStub.getPersonMap().get(id);
        assertEquals(new Integer(id), newPerson.getId());
        assertEquals("New Name",      newPerson.getName());
        assertEquals((Integer) 60,    newPerson.getAge());
    }
    
    @Test
    public void personIsUpdated() {
        Person person = myService.getPersonById(3);
        person.setName("Changed Name");
        person.setAge(70);
        myService.savePerson(person);
        
        Person newPerson = personDaoStub.getPersonMap().get(3);
        assertEquals((Integer) 3,    newPerson.getId());
        assertEquals("Changed Name", newPerson.getName());
        assertEquals((Integer) 70,   newPerson.getAge());
    }
    
    @Test
    public void personIsDeleted() {
        Person person = myService.getPersonById(3);
        assertNotNull(person);
        myService.deletePerson(person);
        assertNull(personDaoStub.getPersonMap().get(3));
    }
}

PersonControllerTest.java
package com.lishman.testing.web;

import static org.junit.Assert.assertEquals;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.ModelAndViewAssert;
import org.springframework.web.servlet.ModelAndView;

import com.lishman.testing.domain.Person;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("WebTest-context.xml")
public class PersonControllerTest {

    @Autowired
    private PersonController personController;
    
    @Test
    public void allPersonsAreReturned() { 
        MockHttpServletRequest request = new MockHttpServletRequest();
        assertEquals(4, personController.getPersonList(request).size());
        assertEquals("my_value", request.getAttribute("my_attribute"));
    }
    
    @Test
    public void personForSpecifiedIdIsReturned() {
        ModelAndView mav = personController.getPerson(3);
        
        ModelAndViewAssert.assertViewName(mav, "person-details");
        ModelAndViewAssert.assertModelAttributeAvailable(mav, "person");

        Person tag = (Person) mav.getModelMap().get("person");
        assertEquals(new Integer(3), tag.getId());
        assertEquals("Third Person", tag.getName());
        assertEquals((Integer) 30,   tag.getAge());    
    }
}
PersonFormTest.java
package com.lishman.testing.web;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.bind.support.SimpleSessionStatus;

import com.lishman.testing.data.stub.PersonDaoStub;
import com.lishman.testing.domain.Person;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("WebTest-context.xml")
public class PersonFormTest {

    @Autowired
    private PersonForm personForm;
    
    @Autowired
    private PersonDaoStub personDao;
    
    @Test
    public void emptyPersonObjectIsReturnedWhenNoIdIsSupplied() {
        Person person = personForm.setUpForm(null);
        assertNull(person.getId());
        assertNull(person.getName());
        assertNull(person.getAge());
     }
    
    @Test
    public void populatedPersonObjectIsReturnedWhenIdIsSupplied() {
        Person person = personForm.setUpForm(3);
        assertEquals((Integer) 3,    person.getId());
        assertEquals("Third Person", person.getName());
        assertEquals((Integer) 30,   person.getAge());
    }
    
    @Test
    @DirtiesContext
    public void validPersonIsCreated() {
        Person newPerson = new Person("New Name", 60);
        BindingResult result = new BeanPropertyBindingResult(newPerson, "person");
        SessionStatus status = new SimpleSessionStatus();
        
        String viewName = personForm.save(newPerson, result, status);
        
        assertEquals("redirect:person-list", viewName);
        assertEquals(0, result.getAllErrors().size());
        assertTrue(status.isComplete());
        
        int idJustInserted = personDao.getLastId();
        Person person = personDao.getPersonMap().get(idJustInserted);
        assertEquals("New Name",    person.getName());
        assertEquals((Integer) 60,  person.getAge());
        assertEquals(5, personDao.getPersonMap().size());
    }
    
    @Test
    @DirtiesContext
    public void validPersonIsUpdated() {
        Person newPerson = new Person(1, "Changed Name", 70);
        BindingResult result = new BeanPropertyBindingResult(newPerson, "person");
        SessionStatus status = new SimpleSessionStatus();
        
        String viewName = personForm.save(newPerson, result, status);
        
        assertEquals("redirect:person-list", viewName);
        assertEquals(0, result.getAllErrors().size());
        assertTrue(status.isComplete());
        
        Person person = personDao.getPersonMap().get(1);
        assertEquals("Changed Name",    person.getName());
        assertEquals((Integer) 70,      person.getAge());
        assertEquals(4, personDao.getPersonMap().size());
    }
    
    @Test
    @DirtiesContext
    public void personIsDeleted() {
        Model model = new ExtendedModelMap();
        model.addAttribute(new Person(2, "Second Person", 20));
        SessionStatus status = new SimpleSessionStatus();
        
        String viewName = personForm.delete(model, status);
        
        assertEquals("redirect:person-list", viewName);       
        assertTrue(status.isComplete());
        
        assertNull(personDao.getPersonMap().get(2));
        assertEquals(3, personDao.getPersonMap().size());
    }
    
    @Test
    public void newPersonIsRejectedIfAlreadyExists() {
        Person newPerson = new Person("First Person", 10);
        BindingResult result = new BeanPropertyBindingResult(newPerson, "person");
        SessionStatus status = new SimpleSessionStatus();
        
        assertEquals("person-form", personForm.save(newPerson, result, status));
        
        assertEquals(1, result.getAllErrors().size());
        assertTrue(result.hasFieldErrors("name"));
        assertEquals("already exists", result.getFieldError("name").getDefaultMessage());
        assertFalse(status.isComplete());
    }
    
    @Test
    public void updatedPersonIsRejectedIfAlreadyExists() {
        Person newPerson = new Person(1, "Second Person", 10);
        BindingResult result = new BeanPropertyBindingResult(newPerson, "person");
        SessionStatus status = new SimpleSessionStatus();
        
        assertEquals("person-form", personForm.save(newPerson, result, status));
        
        assertEquals(1, result.getAllErrors().size());
        assertTrue(result.hasFieldErrors("name"));
        assertEquals("already exists", result.getFieldError("name").getDefaultMessage());
        assertFalse(status.isComplete());
    }
    

}

WebTest-context.xml
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">

  <context:component-scan base-package="com.lishman.testing.web"/>   
  <context:component-scan base-package="com.lishman.testing.service"/>   
  <context:component-scan base-package="com.lishman.testing.domain"/> 
  <context:component-scan base-package="com.lishman.testing.data.stub"/>
  
</beans>
FullTestSuite.java
package com.lishman.testing;

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

import com.lishman.testing.data.hibernate.HibernatePersonDaoTest;
import com.lishman.testing.data.jpa.JpaPersonDaoTest;
import com.lishman.testing.domain.PersonTest;
import com.lishman.testing.domain.PersonValidatorTest;
import com.lishman.testing.service.MyServiceTest;
import com.lishman.testing.web.PersonControllerTest;
import com.lishman.testing.web.PersonFormTest;

@RunWith(Suite.class)
@SuiteClasses({

    // data
    HibernatePersonDaoTest.class,
    JpaPersonDaoTest.class,
    
    // domain
    PersonTest.class,
    PersonValidatorTest.class,
    
    // service
    MyServiceTest.class,
    
    // web
    PersonControllerTest.class,
    PersonFormTest.class,
    
})

public class FullTestSuite {

}

jdbc.properties
jdbc.driverClassName=oracle.jdbc.OracleDriver
jdbc.url=jdbc:oracle:thin:@my_test_server:1521:my_db
jdbc.username=my_user
jdbc.password=my_password

hibernate.generate_statistics=true
hibernate.show_sql=true
hibernate.format_sql=true
hibernate.dialect=org.hibernate.dialect.Oracle10gDialect
person-form.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<html>

  <head>
    <title>Person Details</title>
  </head>
  
  <body>
    <h1>Person Details</h1>
    
    <form:form modelAttribute="person" action="person-form" method="post">
    
      <c:choose>
        <c:when test="${person.new}">
          <button type="submit" name="save">
            create
          </button>
        </c:when>
        <c:otherwise>
          <button type="submit" name="save">
            update
          </button>
          <button type="submit" name="delete" onclick="return confirm('Delete this person?')">
            delete
          </button>
        </c:otherwise>
      </c:choose>
      
      <p>
        Name<br>
        <form:input path="name"><br/>
        <form:errors path="name">
      </p>

      <p>
        Age<br>
        <form:input path="age"><br/>
        <form:errors path="age">
      </p>

    </form:form>
    
    <a href="<c:url value=" /app/person-list.html">" >
      «cancel
    </a>
    
  </body>
</html>

person-list.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>

  <head>
    <title>Person List</title>
  </head>
  
  <body>
    <h1>Person List</h1>
    
      <a href="<c:url value=" /app/person-form.html">">
        <button>create</button>
      </a>

      <table>
        <c:forEach items="${persons}" var="person">
          <tr>
            <td>
              <a href="<c:url value='/app/person-form.html?id=${person.id}'/>">
                ${person.name}
              </a>
            </td>     
          </tr>
        </c:forEach>
      </table>
    
  </body>
</html>

hibernate-config.xml
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

  <context:component-scan base-package="com.lishman.testing.data.hibernate"/>
  
  <tx:annotation-driven transaction-manager="txnManager"/>

  <context:property-placeholder location="WEB-INF/spring/data/jdbc.properties"/>

  <bean id="dataSource"
        class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close"
        p:driverClassName="${jdbc.driverClassName}"
        p:url="${jdbc.url}"
        p:username="${jdbc.username}"
        p:password="${jdbc.password}"
        p:maxActive="${dbcp.maxActive}"
        p:maxIdle="${dbcp.maxIdle}"
        p:maxWait="${dbcp.maxWait}"/>

  <bean id="sessionFactory"
        class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
        p:dataSource-ref="dataSource"
        p:configurationClass="org.hibernate.cfg.AnnotationConfiguration"
        p:packagesToScan="com.lishman.testing.domain">
    <property name="hibernateProperties">
      <props>
        <prop key="hibernate.dialect">${hibernate.dialect}</prop>
        <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
        <prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
        <prop key="hibernate.generate_statistics">${hibernate.generate_statistics}</prop>
      </props>
    </property>
    <property name="eventListeners">
      <map>
        <entry key="merge">
          <bean class="org.springframework.orm.hibernate3.support.IdTransferringMergeEventListener"/>
        </entry>
      </map>
    </property>

  </bean>

  <bean id="txnManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager"
        p:sessionFactory-ref="sessionFactory"/>

</beans>
jdbc.properties
jdbc.driverClassName=oracle.jdbc.OracleDriver
jdbc.url=jdbc:oracle:thin:@my_server:1521:my_db
jdbc.username=my_user
jdbc.password=my_password

dbcp.maxActive=100
dbcp.maxIdle=30
dbcp.maxWait=20000

hibernate.generate_statistics=true
hibernate.show_sql=false
hibernate.format_sql=false
hibernate.dialect=org.hibernate.dialect.Oracle10gDialect
jpa-config.xml
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

  <context:component-scan base-package="com.lishman.testing.data.jpa"/>

  <context:property-placeholder location="WEB-INF/spring/data/jdbc.properties"/>
  
  <bean id="dataSource"
        class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close"
        p:driverClassName="${jdbc.driverClassName}"
        p:url="${jdbc.url}"
        p:username="${jdbc.username}"
        p:password="${jdbc.password}"
        p:maxActive="${dbcp.maxActive}"
        p:maxIdle="${dbcp.maxIdle}"
        p:maxWait="${dbcp.maxWait}"/>

  <bean id="entityManagerFactory"
        class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
        p:persistenceUnitName="testing"
        p:dataSource-ref="dataSource"
        p:persistenceXmlLocation="classpath:META-INF/jpa-persistence.xml">
    <property name="jpaVendorAdapter">
      <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
            p:databasePlatform="${hibernate.dialect}"
            p:showSql="${hibernate.show_sql}"/>
    </property>
  </bean>

  <tx:annotation-driven transaction-manager="txnManager"/>

  <bean id="txnManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
  </bean>
  
  <bean id="jpaTemplate" class="org.springframework.orm.jpa.JpaTemplate">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
  </bean>
  
</beans>
app-config.xml
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">

  <context:component-scan base-package="com.lishman.testing.service"/>
  <context:component-scan base-package="com.lishman.testing.domain"/>
  
  <import resource="mvc-config.xml"/>
  
  <import resource="data/jpa-config.xml"/>

</beans>
mvc-config.xml
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">

  <mvc:annotation-driven/>
  
  <context:component-scan base-package="com.lishman.testing.web"/>
        
  <bean id="viewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver"
        p:prefix="/WEB-INF/jsp/"
        p:suffix=".jsp"/>

</beans>
log4j.properties
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### direct messages to file hibernate.log ###
#log4j.appender.file=org.apache.log4j.FileAppender
#log4j.appender.file.File=hibernate.log
#log4j.appender.file.layout=org.apache.log4j.PatternLayout
#log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###

log4j.logger=org.springframework=INFO

log4j.rootLogger=INFO, stdout

#log4j.logger.helloworld.web.HelloWorldController=info, stdout

#log4j.logger.org.hibernate=info
log4j.logger.org.hibernate=info

### log HQL query parser activity
#log4j.logger.org.hibernate.hql.ast.AST=debug

### log just the SQL
log4j.logger.org.hibernate.SQL=info

### log JDBC bind parameters ###
#log4j.logger.org.hibernate.type=info
log4j.logger.org.hibernate.type=info

### log schema export/update ###
log4j.logger.org.hibernate.tool.hbm2ddl=info

### log HQL parse trees
#log4j.logger.org.hibernate.hql=debug

### log cache activity ###
#log4j.logger.org.hibernate.cache=debug

### log transaction activity
#log4j.logger.org.hibernate.transaction=debug

### log JDBC resource acquisition
#log4j.logger.org.hibernate.jdbc=debug

### enable the following line if you want to track down connection ###
### leakages when using DriverManagerConnectionProvider ###
#log4j.logger.org.hibernate.connection.DriverManagerConnectionProvider=trace
web.xml
<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         version="2.4"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

  <servlet>
    <servlet-name>test</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>
        /WEB-INF/spring/app-config.xml
      </param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>test</servlet-name>
    <url-pattern>/app/*</url-pattern>
  </servlet-mapping>

  <welcome-file-list>
    <welcome-file>app/person-list</welcome-file>
  </welcome-file-list>

</web-app>
schema.sql

DROP TABLE person;
DROP SEQUENCE person_seq;

CREATE TABLE person (
  prsn_id       NUMBER,
  prsn_name     VARCHAR2(100)  NOT NULL,
  prsn_age      NUMBER         NOT NULL,
  CONSTRAINT person_pk PRIMARY KEY (prsn_id),
  CONSTRAINT person_uk UNIQUE (prsn_name),
  CONSTRAINT person_cc CHECK (prsn_age BETWEEN 10 AND 100)
);

CREATE SEQUENCE person_seq START WITH 100;

CREATE OR REPLACE TRIGGER person_trg
  BEFORE INSERT ON person FOR EACH ROW
BEGIN 
    IF :NEW.prsn_id IS NULL THEN
      SELECT person_seq.NEXTVAL INTO :NEW.prsn_id FROM DUAL;
    END IF;
END;
/
unit-test-data.sql
TRUNCATE TABLE person;

INSERT INTO person (prsn_id, prsn_name, prsn_age) VALUES (1, 'First Person',  10);
INSERT INTO person (prsn_id, prsn_name, prsn_age) VALUES (2, 'Second Person', 20);
INSERT INTO person (prsn_id, prsn_name, prsn_age) VALUES (3, 'Third Person',  30);
INSERT INTO person (prsn_id, prsn_name, prsn_age) VALUES (4, 'Fourth Person', 40);

COMMIT;

TestContext Framework

In this topic we test an application using JUnit 4 and the Spring TestContext framework.

The TestContext framework provides:
  • Dependency injection of test fixtures.
  • IoC container caching between tests.
  • Transaction management.
  • Support classes.

A simple web based application, which performs CRUD operations on a PERSONperson:eq(2) table, allows us to demonstrate the Spring testing features at each level of the software stack.

We use SpringJUnit4ClassRunnerSpringJUnit4ClassRunner:eq(1) to integrate the TestContext framework with JUnit 4.5+, and @ContextConfiguration to tell Spring where to find the application context file(s)WebTest-context.xml for our tests.
The context file identifies the directories to be scanned for the components to be used in the tests.
These components are then wired up and injected@Autowired into our class as test fixtures.

Once loaded, application contexts are cached across tests to improve performance.

We can use the @DirtiesContext annotation to force Spring to reload the contexts after a test, should the need arise.

In this example, the application context is reloaded to ensure that the DAO stub is reset to its initial state if it has been changed by a test.

Annotations

The TestContext framework also provides some useful annotations to simplify unit and integration testing.

For example, @Repeat indicates that a test must be executed repeatedly, and @Timed checks that all these tests (including set up and tear down) are completed within the specified timemillis.
The TestContext framework relies on default listeners to provide functionality for dependency injection, dirtying the cache and transaction management (more on this later).

In this test we require none of these features but we do use SpringJUnit4ClassRunnerSpringJUnit4ClassRunner:eq(1) for @Repeat and @Timed.

Therefore, we override the default configuration by supplying @TestExecutionListeners with an empty set of listeners. This allows us to use SpringJUnit4ClassRunnerSpringJUnit4ClassRunner:eq(1) for @Repeat and @Timed, but does not cause Spring to fail when it cannot find an application context file.

Mock and Utility Classes

For web testing, Spring provides mock objects such as MockHttpServletRequestMockHttpServletRequest:eq(1), MockHttpSession, MockMultipartFile etc.
ModelAndViewAssertModelAndViewAssert:gt(0) contains a collection of assertions which simplify testing with ModelAndView objects.

ReflectionTestUtilsReflectionTestUtils:gt(0) is a utility class for working with the reflection API.

For example, in the service layer test we need inject a DAO stubPersonDaoStub:gt(0):lt(2) into our service implementation, rather than a real DAO that connects to the database.

However, this time we don't depend on auto-wiring provided by the Spring TestContext framework for dependency injection. Instead we use ReflectionTestUtilsReflectionTestUtils:gt(0) to manually inject the DAO into a private variable.

Support Classes

As an alternative to SpringJUnit4ClassRunner we can extend one of the Spring TestContext support classes.
AbstractJUnit4SpringContextTests integrates the Spring TestContext framework and provides access to the application context for explicit bean lookups or to test the state of the context.

AbstractTransactionalJUnit4SpringContextTestsAbstractTransactional:eq(1) extends this class and adds transaction management and convenience methods for checking database related test results.
We still include the @ContextConfiguration annotation, but this time, rather than naming the context file explicitly, we rely on the default name of [class-name]-context.xml.

Data Access

When tests are executed in a transactional contextAbstractTransactional:eq(1), the database is automatically rolled back after each test. This is a common practice used to restore the database to its initial state before the test was executed.

You can use the @Rollback(false) annotation on a test method to change this default behavior and commit the transaction.
The TestContext support classAbstractTransactional:eq(1) also provides convenience methods such as countRowsInTable, and access to simpleJdbcTemplate for checking the contents of the database within the same transaction as the test.

It should be noted that when testing with an ORM framework, any changes to entity objects must flushedflush to the database if they are to be picked up by simpleJdbcTemplate etc.

Fortunately, in our case, the application does the flush, otherwise we would need to inject the JpaTemplate or SessionFactory into our test class to perform the flush manually.

Inheritance

When testing the persistence layer, we take advantage of the support for inheritance in the TestContext framework.
PersonDaoTest contains all the tests for the persistence layer, which is implemented in both native Hibernate and JPA.
JpaPersonDaoTest and HibernatePersonDaoTest both extend PersonDaoTest, so that the same set of tests can be executed for each implementation.

Configuration information is also split and placed in the appropriate context files.

PersonDaoTest-context.xml contains configuration information which is inherited by the Hibernate and JPA integration tests.
HibernatePersonDaoTest-context.xml extends this by adding specific configuration for native Hibernate access.
JpaPersonDaoTest-context.xml includes the configuration for JPA.
Home  |  Getting Started  |  Controllers  |  Views  |  Forms  |  REST  |  Testing  |  Configuration  |  MVC Walkthrough  |   lishblog  |  Email Lishy