This post shows you how to Unit and integration test your Data layer, service layer and view layer in a Spring 4 MVC + Hibernate 4 + MySQL + Maven integration application.
This post is continuation of Previous post. In this post we will learn how to add unit and integration test in SpringMVC and hibernate based maven project using TestNG
, mockito
, spring-test
, DBUnit
& H2 database
. To know more about testing with TestNG in general, please refer our TestNG Tutorials. Let’s get going.
Following technologies being used:
Let’s begin.
It’s same directory structure as in previous post. What’s new is that we have added src/test/java & src/test/resources folders and testing artifacts. We will maintain the same package structure in test as in src folder.
Following will be the final project structure:[Note that src/main part is hidden in order to show everything in one image]
This is same pom.xml as declared in previous post
<?xml version="1.0"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <groupId>com.websystique.springmvc</groupId> <artifactId>SpringHibernateExample</artifactId> <packaging>war</packaging> <version>1.0.0</version> <name>SpringHibernateExample</name> <properties> <springframework.version>4.0.6.RELEASE</springframework.version> <hibernate.version>4.3.6.Final</hibernate.version> <mysql.connector.version>5.1.31</mysql.connector.version> <joda-time.version>2.3</joda-time.version> <testng.version>6.9.4</testng.version> <mockito.version>1.10.19</mockito.version> <h2.version>1.4.187</h2.version> <dbunit.version>2.2</dbunit.version> </properties> <dependencies> <!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${springframework.version}</version> </dependency> <!-- Hibernate --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>${hibernate.version}</version> </dependency> <!-- jsr303 validation --> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>1.1.0.Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.1.3.Final</version> </dependency> <!-- MySQL --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.connector.version}</version> </dependency> <!-- Joda-Time --> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>${joda-time.version}</version> </dependency> <!-- To map JodaTime with database type --> <dependency> <groupId>org.jadira.usertype</groupId> <artifactId>usertype.core</artifactId> <version>3.0.0.CR1</version> </dependency> <!-- Servlet+JSP+JSTL --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- Testing dependencies --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${springframework.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>${testng.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>${mockito.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>${h2.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>dbunit</groupId> <artifactId>dbunit</artifactId> <version>${dbunit.version}</version> <scope>test</scope> </dependency> </dependencies> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.4</version> <configuration> <warSourceDirectory>src/main/webapp</warSourceDirectory> <warName>SpringHibernateExample</warName> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </pluginManagement> <finalName>SpringHibernateExample</finalName> </build> </project>
Just to recall that dependencies spring-test, testNG, mockito, h2 & DBUnit are all here for Testing purpose only.
Spring-test
: We will be using spring-test annotations in our test classes.
TestNG
: We will be using TestNG as our testing framework (& it’s my favorite anyway).
Mockito
: We would be time to time doing some mocking, like mocking dao when testing service.
DBUnit
: We will use DBUnit to mange our data during data/dao layer testing
H2 Database
: For database layer, it’s more of integration-test than unit-test. IMO, unit tests does not bring real value while testing data layer. We will be using in-memory H2 database to do our integration-tests.
Let’s Start some real testing.
package com.websystique.springmvc.controller; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.when; import static org.mockito.Mockito.verify; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import org.joda.time.LocalDate; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; import static org.mockito.Mockito.atLeastOnce; import org.springframework.context.MessageSource; import org.springframework.ui.ModelMap; import org.springframework.validation.BindingResult; import org.testng.Assert; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import com.websystique.springmvc.model.Employee; import com.websystique.springmvc.service.EmployeeService; public class AppControllerTest { @Mock EmployeeService service; @Mock MessageSource message; @InjectMocks AppController appController; @Spy List<Employee> employees = new ArrayList<Employee>(); @Spy ModelMap model; @Mock BindingResult result; @BeforeClass public void setUp(){ MockitoAnnotations.initMocks(this); employees = getEmployeeList(); } @Test public void listEmployees(){ when(service.findAllEmployees()).thenReturn(employees); Assert.assertEquals(appController.listEmployees(model), "allemployees"); Assert.assertEquals(model.get("employees"), employees); verify(service, atLeastOnce()).findAllEmployees(); } @Test public void newEmployee(){ Assert.assertEquals(appController.newEmployee(model), "registration"); Assert.assertNotNull(model.get("employee")); Assert.assertFalse((Boolean)model.get("edit")); Assert.assertEquals(((Employee)model.get("employee")).getId(), 0); } @Test public void saveEmployeeWithValidationError(){ when(result.hasErrors()).thenReturn(true); doNothing().when(service).saveEmployee(any(Employee.class)); Assert.assertEquals(appController.saveEmployee(employees.get(0), result, model), "registration"); } @Test public void saveEmployeeWithValidationErrorNonUniqueSSN(){ when(result.hasErrors()).thenReturn(false); when(service.isEmployeeSsnUnique(anyInt(), anyString())).thenReturn(false); Assert.assertEquals(appController.saveEmployee(employees.get(0), result, model), "registration"); } @Test public void saveEmployeeWithSuccess(){ when(result.hasErrors()).thenReturn(false); when(service.isEmployeeSsnUnique(anyInt(), anyString())).thenReturn(true); doNothing().when(service).saveEmployee(any(Employee.class)); Assert.assertEquals(appController.saveEmployee(employees.get(0), result, model), "success"); Assert.assertEquals(model.get("success"), "Employee Axel registered successfully"); } @Test public void editEmployee(){ Employee emp = employees.get(0); when(service.findEmployeeBySsn(anyString())).thenReturn(emp); Assert.assertEquals(appController.editEmployee(anyString(), model), "registration"); Assert.assertNotNull(model.get("employee")); Assert.assertTrue((Boolean)model.get("edit")); Assert.assertEquals(((Employee)model.get("employee")).getId(), 1); } @Test public void updateEmployeeWithValidationError(){ when(result.hasErrors()).thenReturn(true); doNothing().when(service).updateEmployee(any(Employee.class)); Assert.assertEquals(appController.updateEmployee(employees.get(0), result, model,""), "registration"); } @Test public void updateEmployeeWithValidationErrorNonUniqueSSN(){ when(result.hasErrors()).thenReturn(false); when(service.isEmployeeSsnUnique(anyInt(), anyString())).thenReturn(false); Assert.assertEquals(appController.updateEmployee(employees.get(0), result, model,""), "registration"); } @Test public void updateEmployeeWithSuccess(){ when(result.hasErrors()).thenReturn(false); when(service.isEmployeeSsnUnique(anyInt(), anyString())).thenReturn(true); doNothing().when(service).updateEmployee(any(Employee.class)); Assert.assertEquals(appController.updateEmployee(employees.get(0), result, model, ""), "success"); Assert.assertEquals(model.get("success"), "Employee Axel updated successfully"); } @Test public void deleteEmployee(){ doNothing().when(service).deleteEmployeeBySsn(anyString()); Assert.assertEquals(appController.deleteEmployee("123"), "redirect:/list"); } public List<Employee> getEmployeeList(){ Employee e1 = new Employee(); e1.setId(1); e1.setName("Axel"); e1.setJoiningDate(new LocalDate()); e1.setSalary(new BigDecimal(10000)); e1.setSsn("XXX111"); Employee e2 = new Employee(); e2.setId(2); e2.setName("Jeremy"); e2.setJoiningDate(new LocalDate()); e2.setSalary(new BigDecimal(20000)); e2.setSsn("XXX222"); employees.add(e1); employees.add(e2); return employees; } }
Run above test class using TestNG Eclipse Plugin or Maven [mvn clean test e.g.]
Here i used TestNG Eclipse plugin to run this test class.
PASSED: deleteEmployee PASSED: editEmployee PASSED: listEmployees PASSED: newEmployee PASSED: saveEmployeeWithSuccess PASSED: saveEmployeeWithValidationError PASSED: saveEmployeeWithValidationErrorNonUniqueSSN PASSED: updateEmployeeWithSuccess PASSED: updateEmployeeWithValidationError PASSED: updateEmployeeWithValidationErrorNonUniqueSSN =============================================== Default test Tests run: 10, Failures: 0, Skips: 0 ===============================================
Explanation of AppControllerTest class:
If you revisit AppController class in Previous post, you will see that AppController basically depends on EmployeeService , MessageSource, Employee, ModelMap & BindingResult to fullfill all of it’s duties. Each of the AppController method is using only of these objects to do it’s real job.
So in order to test AppController, we would need to provide these dependencies. In our example, we do it using Mockito
framework. We provide mock of EmployeeService & MessageSource by applying @Mock
annotation on them. We also provide spy objects of ModelMap , BindingResult & Employee by applying @Spy
annotations on them.
It’s important to understand that Mockito’s @Mock objects are not real instances, they are just bare-bones of instance created using Class of type. But their main capability is that they can remember all the interactions [operations performed] on them.
@Spy objects are on the other hand real instances, but with additional capabilities of remembering all the interactions [operations performed] on them.
@InjectMocks
creates an instance of the class and injects the mocks that are created with the @Mock/@Spy objects in it.
MockitoAnnotations.initMocks(this);
initializes objects annotated with Mockito annotations [@Mock, @Spy, @Captor, @InjectMocks]
Make sure to call MockitoAnnotations.initMocks when using Mockito annotations, else those mocks will be useless for your tests.
Annotations @Test & @BeforeClass
are TestNG specific annotations.
Assert
is the TestNG api for doing assertions on expected result and actual result.
when..then & verify are popular stubbing and verification techniques used in tests to define the behavior and then optionally verifying that behavior was indeed executed.
There are many more. Please refer to TestNG tutorial for in depth details about writing tests using TestNG, mockito and other supportive libraries.
package com.websystique.springmvc.service; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.verify; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import static org.mockito.Mockito.when; import org.joda.time.LocalDate; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; import org.testng.Assert; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import com.websystique.springmvc.dao.EmployeeDao; import com.websystique.springmvc.model.Employee; public class EmployeeServiceImplTest { @Mock EmployeeDao dao; @InjectMocks EmployeeServiceImpl employeeService; @Spy List<Employee> employees = new ArrayList<Employee>(); @BeforeClass public void setUp(){ MockitoAnnotations.initMocks(this); employees = getEmployeeList(); } @Test public void findById(){ Employee emp = employees.get(0); when(dao.findById(anyInt())).thenReturn(emp); Assert.assertEquals(employeeService.findById(emp.getId()),emp); } @Test public void saveEmployee(){ doNothing().when(dao).saveEmployee(any(Employee.class)); employeeService.saveEmployee(any(Employee.class)); verify(dao, atLeastOnce()).saveEmployee(any(Employee.class)); } @Test public void updateEmployee(){ Employee emp = employees.get(0); when(dao.findById(anyInt())).thenReturn(emp); employeeService.updateEmployee(emp); verify(dao, atLeastOnce()).findById(anyInt()); } @Test public void deleteEmployeeBySsn(){ doNothing().when(dao).deleteEmployeeBySsn(anyString()); employeeService.deleteEmployeeBySsn(anyString()); verify(dao, atLeastOnce()).deleteEmployeeBySsn(anyString()); } @Test public void findAllEmployees(){ when(dao.findAllEmployees()).thenReturn(employees); Assert.assertEquals(employeeService.findAllEmployees(), employees); } @Test public void findEmployeeBySsn(){ Employee emp = employees.get(0); when(dao.findEmployeeBySsn(anyString())).thenReturn(emp); Assert.assertEquals(employeeService.findEmployeeBySsn(anyString()), emp); } @Test public void isEmployeeSsnUnique(){ Employee emp = employees.get(0); when(dao.findEmployeeBySsn(anyString())).thenReturn(emp); Assert.assertEquals(employeeService.isEmployeeSsnUnique(emp.getId(), emp.getSsn()), true); } public List<Employee> getEmployeeList(){ Employee e1 = new Employee(); e1.setId(1); e1.setName("Axel"); e1.setJoiningDate(new LocalDate()); e1.setSalary(new BigDecimal(10000)); e1.setSsn("XXX111"); Employee e2 = new Employee(); e2.setId(2); e2.setName("Jeremy"); e2.setJoiningDate(new LocalDate()); e2.setSalary(new BigDecimal(20000)); e2.setSsn("XXX222"); employees.add(e1); employees.add(e2); return employees; } }
Run above test class using TestNG Eclipse Plugin or Maven [mvn clean test e.g.]
PASSED: deleteEmployeeBySsn PASSED: findAllEmployees PASSED: findById PASSED: findEmployeeBySsn PASSED: isEmployeeSsnUnique PASSED: saveEmployee PASSED: updateEmployee =============================================== Default test Tests run: 7, Failures: 0, Skips: 0 ===============================================
Explanation of EmployeeServiceImplTest class:
If you revisit EmployeeServiceImpl class in Previous post, you will see that EmployeeServiceImpl basically depends on EmployeeDao & Employee to fulfill all of it’s duties. Each of the EmployeeServiceImplmethod is using only of these objects to do it’s real job.
As explained above, in order to test EmployeeServiceImpl, we would need to provide these dependencies. In our example, we do it using Mockito framework. We provide mock of EmployeeDao by applying @Mock
annotation on it. We also provide spy objects of Employee by applying @Spy
annotations on them.
@InjectMocks
creates an instance of the class and injects the mocks that are created with the @Mock/@Spy objects in it.
MockitoAnnotations.initMocks(this);
initializes objects annotated with Mockito annotations [@Mock, @Spy, @Captor, @InjectMocks]
So, Make sure to call MockitoAnnotations.initMocks when using Mockito annotations, else those mocks will be useless for your tests.
Please refer to TestNG tutorial for in depth details about writing tests using TestNG, mockito and other supportive libraries.
Testing DAO or data layer is always a subject of debate. What exactly we want to test? Are we just testing the methods from DAO implementation class and making sure that each and every line of code in those methods is covered?
If we think in terms of unit-test, than our goal becomes testing every line of DAO code while really mocking all the external systems/dependencies. IMO, we can’t truly test a data-layer without really interacting with the database itself. And then it becomes an integration test.
Anyway, we will perform integration-test on our DAO layer to make sure that it works as expected. We will be using in-memory H2 database to do our integration-tests.
If we look back at actual dao/data-layer class EmployeeDaoImpl in Previous post, it relies on hibernate for database interactions. There all hibernate setup related stuff was defined in HibernateConfiguration class. We will need similar setup in tests in order to connect to database and perform hibernate session related operations during our tests.
Below class is a setup class [with Annotations] for all hibernate configuration related activities during tests.
package com.websystique.springmvc.configuration; import java.util.Properties; import javax.sql.DataSource; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import org.springframework.jdbc.datasource.DriverManagerDataSource; import org.springframework.orm.hibernate4.HibernateTransactionManager; import org.springframework.orm.hibernate4.LocalSessionFactoryBean; import org.springframework.transaction.annotation.EnableTransactionManagement; /* * This class is same as real HibernateConfiguration class in sources. * Only difference is that method dataSource & hibernateProperties * implementations are specific to Hibernate working with H2 database. */ @Configuration @EnableTransactionManagement @ComponentScan({ "com.websystique.springmvc.dao" }) public class HibernateTestConfiguration { @Autowired private Environment environment; @Bean public LocalSessionFactoryBean sessionFactory() { LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean(); sessionFactory.setDataSource(dataSource()); sessionFactory.setPackagesToScan(new String[] { "com.websystique.springmvc.model" }); sessionFactory.setHibernateProperties(hibernateProperties()); return sessionFactory; } @Bean(name = "dataSource") public DataSource dataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("org.h2.Driver"); dataSource.setUrl("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE"); dataSource.setUsername("sa"); dataSource.setPassword(""); return dataSource; } private Properties hibernateProperties() { Properties properties = new Properties(); properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect"); properties.put("hibernate.hbm2ddl.auto", "create-drop"); return properties; } @Bean @Autowired public HibernateTransactionManager transactionManager(SessionFactory s) { HibernateTransactionManager txManager = new HibernateTransactionManager(); txManager.setSessionFactory(s); return txManager; } }
Above class is exactly same as HibernateConfiguration class defined in src in Previous post. Only difference is that mathods dataSource() & hibernateProperties() are implemented specific to Hibernate & H2 combination. I choose to make a separate test configuration class and not to pollute existing class in Sources with testing related stuff.
It does everything exactly same as the one in Sources folder: it creates a SessionFacoty using a dataSource which is configured to work with in-memory database H2. In order to make hibernate work with H2, we also need to specify the dialect being used [H2 Dialect].
This SessionFactory will be injected in our AbstractDao class defined in Previous post. And from then on, the actual DAO implementation classes [ EmployeeDaoImpl] will use this sessionFactory when running tests against them.
Additionally, we will be using DBUnit
to clean-insert sample data in test database[H2] before each test case execution, in order to prepare database before each Dao method execution. This way we make sure that the tests method do not interfere with each other.
Below is a sample class which will act as a base class for all our test classes.
package com.websystique.springmvc.dao; import javax.sql.DataSource; import org.dbunit.database.DatabaseDataSourceConnection; import org.dbunit.database.IDatabaseConnection; import org.dbunit.dataset.IDataSet; import org.dbunit.operation.DatabaseOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests; import org.testng.annotations.BeforeMethod; import com.websystique.springmvc.configuration.HibernateTestConfiguration; @ContextConfiguration(classes = { HibernateTestConfiguration.class }) public abstract class EntityDaoImplTest extends AbstractTransactionalTestNGSpringContextTests { @Autowired DataSource dataSource; @BeforeMethod public void setUp() throws Exception { IDatabaseConnection dbConn = new DatabaseDataSourceConnection( dataSource); DatabaseOperation.CLEAN_INSERT.execute(dbConn, getDataSet()); } protected abstract IDataSet getDataSet() throws Exception; }
AbstractTransactionalTestNGSpringContextTests
can (at some extent) be considered as JUnit equivalent of RunWith. This abstract class integrates Spring TestContext support in TestNG environment. It requires a class-level @ContextConfiguration
in order to load ApplicationContext using XML configuration files or annotated @Configuration classes.
It also requires a datasource and a transactionManager to be defined in ApplicationContext in order to provide data-access support during testing. We have already defined both datasource & transactionManager in our @Configuration class.
Thanks to transaction support, by default a transaction will be started before each test, and then this transaction will be rolled back at the end of test. You may override the rollback behavior.
Look at setup method annotated with @BeforeMethod
. Method annotated with @BeforeMethod is called before each test, so it is an ideal place to do something which is required before each test. In our case , we want the in-memory database to be clean and predefined sample data to be inserted before each test. We will do it right here.
Additionally, for DBUnit to connect to database in order to perform clean-insert, we have to provide a dataSource for it. That’s why we declared a dataSource here, which will be autowired with dataSource defined in HibernateTestConfiguration class.
As shown in above setUp method, firstly we create a connection to database using dataSource available( which will be test dataSource), and execute clean-insert on DB via DBUnit.
Notice the abstract method getDataSet above. This method will be implemented in our tests classes in order to provide the actual test data to be inserted before each test.
Finally, the actual test class which tests methods from our DAO implementation class.
package com.websystique.springmvc.dao; import java.math.BigDecimal; import org.dbunit.dataset.IDataSet; import org.dbunit.dataset.xml.FlatXmlDataSet; import org.joda.time.LocalDate; import org.springframework.beans.factory.annotation.Autowired; import org.testng.Assert; import org.testng.annotations.Test; import com.websystique.springmvc.model.Employee; public class EmployeeDaoImplTest extends EntityDaoImplTest{ @Autowired EmployeeDao employeeDao; @Override protected IDataSet getDataSet() throws Exception{ IDataSet dataSet = new FlatXmlDataSet(this.getClass().getClassLoader().getResourceAsStream("Employee.xml")); return dataSet; } /* In case you need multiple datasets (mapping different tables) and you do prefer to keep them in separate XML's @Override protected IDataSet getDataSet() throws Exception { IDataSet[] datasets = new IDataSet[] { new FlatXmlDataSet(this.getClass().getClassLoader().getResourceAsStream("Employee.xml")), new FlatXmlDataSet(this.getClass().getClassLoader().getResourceAsStream("Benefits.xml")), new FlatXmlDataSet(this.getClass().getClassLoader().getResourceAsStream("Departements.xml")) }; return new CompositeDataSet(datasets); } */ @Test public void findById(){ Assert.assertNotNull(employeeDao.findById(1)); Assert.assertNull(employeeDao.findById(3)); } @Test public void saveEmployee(){ employeeDao.saveEmployee(getSampleEmployee()); Assert.assertEquals(employeeDao.findAllEmployees().size(), 3); } @Test public void deleteEmployeeBySsn(){ employeeDao.deleteEmployeeBySsn("11111"); Assert.assertEquals(employeeDao.findAllEmployees().size(), 1); } @Test public void deleteEmployeeByInvalidSsn(){ employeeDao.deleteEmployeeBySsn("23423"); Assert.assertEquals(employeeDao.findAllEmployees().size(), 2); } @Test public void findAllEmployees(){ Assert.assertEquals(employeeDao.findAllEmployees().size(), 2); } @Test public void findEmployeeBySsn(){ Assert.assertNotNull(employeeDao.findEmployeeBySsn("11111")); Assert.assertNull(employeeDao.findEmployeeBySsn("14545")); } public Employee getSampleEmployee(){ Employee employee = new Employee(); employee.setName("Karen"); employee.setSsn("12345"); employee.setSalary(new BigDecimal(10980)); employee.setJoiningDate(new LocalDate()); return employee; } }
Below is the content of input file used by DBUnit:
src/test/resources/Employee.xml
<?xml version="1.0" encoding="UTF-8"?> <dataset> <employee id="1" NAME="SAMY" JOINING_DATE="2014-04-16" SALARY="20000" SSN="11111" /> <employee id="2" NAME="TOMY" JOINING_DATE="2014-05-17" SALARY="23000" SSN="11112" /> </dataset>
Run above test class using TestNG Eclipse Plugin or Maven [mvn clean test e.g.]
PASSED: deleteEmployeeByInvalidSsn PASSED: deleteEmployeeBySsn PASSED: findAllEmployees PASSED: findById PASSED: findEmployeeBySsn PASSED: saveEmployee =============================================== Default test Tests run: 6, Failures: 0, Skips: 0 ===============================================
Let’s take saveEmployee test case and understand how things happening here.
properties.put("hibernate.hbm2ddl.auto", "create-drop");
Thanks to this hbm2ddl property, when the SessionFactory is created, the schema related to our Model classes will be validated and exported to database. That means Employee table will be created in H2 database.
@Rollback(true)
annotation. It is defined in [org.springframework.test.annotation.Rollback] Shown below is the output of running all the tests using maven [mvn clean test]
E:\workspace7\SpringHibernateExample>mvn clean test [INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building SpringHibernateExample 1.0.0 [INFO] ------------------------------------------------------------------------ [WARNING] The artifact dbunit:dbunit:jar:2.2 has been relocated to org.dbunit:dbunit:jar:2.2 [INFO] [INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ SpringHibernateExample --- [INFO] Deleting E:\workspace7\SpringHibernateExample\target [INFO] [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ SpringHibernateExample --- [WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] Copying 2 resources [INFO] [INFO] --- maven-compiler-plugin:2.5.1:compile (default-compile) @ SpringHibernateExample --- [WARNING] File encoding has not been set, using platform encoding Cp1252, i.e. build is platform dependent! [INFO] Compiling 10 source files to E:\workspace7\SpringHibernateExample\target\classes [INFO] [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ SpringHibernateExample --- [WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] Copying 1 resource [INFO] [INFO] --- maven-compiler-plugin:2.5.1:testCompile (default-testCompile) @ SpringHibernateExample --- [WARNING] File encoding has not been set, using platform encoding Cp1252, i.e. build is platform dependent! [INFO] Compiling 5 source files to E:\workspace7\SpringHibernateExample\target\test-classes [INFO] [INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ SpringHibernateExample --- [INFO] Surefire report directory: E:\workspace7\SpringHibernateExample\target\surefire-reports ------------------------------------------------------- T E S T S ------------------------------------------------------- Running TestSuite Configuring TestNG with: org.apache.maven.surefire.testng.conf.TestNG652Configurator@556e7212 Jul 12, 2015 1:46:48 AM org.springframework.context.support.GenericApplicationContext prepareRefresh INFO: Refreshing org.springframework.context.support.GenericApplicationContext@8be1456: startup date [Sun Jul 12 01:46:48 CEST 2015]; root of context hierarchy Jul 12, 2015 1:46:48 AM org.springframework.jdbc.datasource.DriverManagerDataSource setDriverClassName INFO: Loaded JDBC driver: org.h2.Driver Jul 12, 2015 1:46:48 AM org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit> INFO: HCANN000001: Hibernate Commons Annotations {4.0.5.Final} Jul 12, 2015 1:46:48 AM org.hibernate.Version logVersion INFO: HHH000412: Hibernate Core {4.3.6.Final} Jul 12, 2015 1:46:48 AM org.hibernate.cfg.Environment <clinit> INFO: HHH000206: hibernate.properties not found Jul 12, 2015 1:46:48 AM org.hibernate.cfg.Environment buildBytecodeProvider INFO: HHH000021: Bytecode provider name : javassist Jul 12, 2015 1:46:49 AM org.hibernate.dialect.Dialect <init> INFO: HHH000400: Using dialect: org.hibernate.dialect.H2Dialect Jul 12, 2015 1:46:49 AM org.hibernate.engine.transaction.internal.TransactionFactoryInitiator initiateService INFO: HHH000399: Using default transaction strategy (direct JDBC transactions) Jul 12, 2015 1:46:49 AM org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory <init> INFO: HHH000397: Using ASTQueryTranslatorFactory Jul 12, 2015 1:46:49 AM org.hibernate.validator.internal.util.Version <clinit> INFO: HV000001: Hibernate Validator 5.1.3.Final Jul 12, 2015 1:46:49 AM org.hibernate.tool.hbm2ddl.SchemaExport execute INFO: HHH000227: Running hbm2ddl schema export Jul 12, 2015 1:46:49 AM org.hibernate.tool.hbm2ddl.SchemaExport execute INFO: HHH000230: Schema export complete Jul 12, 2015 1:46:49 AM org.springframework.orm.hibernate4.HibernateTransactionManager afterPropertiesSet INFO: Using DataSource [org.springframework.jdbc.datasource.DriverManagerDataSource@3e2798e6] of Hibernate SessionFactory for HibernateTransactionManager Jul 12, 2015 1:46:49 AM org.springframework.test.context.transaction.TransactionalTestExecutionListener startNewTransaction INFO: Began transaction (1) for test context [DefaultTestContext@50dcb4b5 testClass = EmployeeDaoImplTest, testInstance = com.websystique.springmvc.dao.Employee DaoImplTest@4fc55da3, testMethod = deleteEmployeeByInvalidSsn@EmployeeDaoImplTest, testException = [null], mergedContextConfiguration = [MergedContextConfigurat ion@49dc008c testClass = EmployeeDaoImplTest, locations = '{}', classes = '{class com.websystique.springmvc.configuration.HibernateTestConfiguration}', contextI nitializerClasses = '[]', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]]; tra nsaction manager [org.springframework.orm.hibernate4.HibernateTransactionManager@aa80d36]; rollback [true] Jul 12, 2015 1:46:50 AM org.springframework.test.context.transaction.TransactionalTestExecutionListener endTransaction INFO: Rolled back transaction after test execution for test context [DefaultTestContext@50dcb4b5 testClass = EmployeeDaoImplTest, testInstance = com.websystique .springmvc.dao.EmployeeDaoImplTest@4fc55da3, testMethod = deleteEmployeeByInvalidSsn@EmployeeDaoImplTest, testException = [null], mergedContextConfiguration = [ MergedContextConfiguration@49dc008c testClass = EmployeeDaoImplTest, locations = '{}', classes = '{class com.websystique.springmvc.configuration.HibernateTestCo nfiguration}', contextInitializerClasses = '[]', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]] Jul 12, 2015 1:46:50 AM org.springframework.test.context.transaction.TransactionalTestExecutionListener startNewTransaction INFO: Began transaction (2) for test context [DefaultTestContext@50dcb4b5 testClass = EmployeeDaoImplTest, testInstance = com.websystique.springmvc.dao.Employee DaoImplTest@4fc55da3, testMethod = deleteEmployeeBySsn@EmployeeDaoImplTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@49d c008c testClass = EmployeeDaoImplTest, locations = '{}', classes = '{class com.websystique.springmvc.configuration.HibernateTestConfiguration}', contextInitiali zerClasses = '[]', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]]; transactio n manager [org.springframework.orm.hibernate4.HibernateTransactionManager@aa80d36]; rollback [true] Jul 12, 2015 1:46:50 AM org.springframework.test.context.transaction.TransactionalTestExecutionListener endTransaction INFO: Rolled back transaction after test execution for test context [DefaultTestContext@50dcb4b5 testClass = EmployeeDaoImplTest, testInstance = com.websystique .springmvc.dao.EmployeeDaoImplTest@4fc55da3, testMethod = deleteEmployeeBySsn@EmployeeDaoImplTest, testException = [null], mergedContextConfiguration = [MergedC ontextConfiguration@49dc008c testClass = EmployeeDaoImplTest, locations = '{}', classes = '{class com.websystique.springmvc.configuration.HibernateTestConfigura tion}', contextInitializerClasses = '[]', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]] Jul 12, 2015 1:46:50 AM org.springframework.test.context.transaction.TransactionalTestExecutionListener startNewTransaction INFO: Began transaction (3) for test context [DefaultTestContext@50dcb4b5 testClass = EmployeeDaoImplTest, testInstance = com.websystique.springmvc.dao.Employee DaoImplTest@4fc55da3, testMethod = findAllEmployees@EmployeeDaoImplTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@49dc00 8c testClass = EmployeeDaoImplTest, locations = '{}', classes = '{class com.websystique.springmvc.configuration.HibernateTestConfiguration}', contextInitializer Classes = '[]', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]]; transaction m anager [org.springframework.orm.hibernate4.HibernateTransactionManager@aa80d36]; rollback [true] Jul 12, 2015 1:46:50 AM org.springframework.test.context.transaction.TransactionalTestExecutionListener endTransaction INFO: Rolled back transaction after test execution for test context [DefaultTestContext@50dcb4b5 testClass = EmployeeDaoImplTest, testInstance = com.websystique .springmvc.dao.EmployeeDaoImplTest@4fc55da3, testMethod = findAllEmployees@EmployeeDaoImplTest, testException = [null], mergedContextConfiguration = [MergedCont extConfiguration@49dc008c testClass = EmployeeDaoImplTest, locations = '{}', classes = '{class com.websystique.springmvc.configuration.HibernateTestConfiguratio n}', contextInitializerClasses = '[]', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]] Jul 12, 2015 1:46:50 AM org.springframework.test.context.transaction.TransactionalTestExecutionListener startNewTransaction INFO: Began transaction (4) for test context [DefaultTestContext@50dcb4b5 testClass = EmployeeDaoImplTest, testInstance = com.websystique.springmvc.dao.Employee DaoImplTest@4fc55da3, testMethod = findById@EmployeeDaoImplTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@49dc008c testC lass = EmployeeDaoImplTest, locations = '{}', classes = '{class com.websystique.springmvc.configuration.HibernateTestConfiguration}', contextInitializerClasses = '[]', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]]; transaction manager [ org.springframework.orm.hibernate4.HibernateTransactionManager@aa80d36]; rollback [true] Jul 12, 2015 1:46:50 AM org.springframework.test.context.transaction.TransactionalTestExecutionListener endTransaction INFO: Rolled back transaction after test execution for test context [DefaultTestContext@50dcb4b5 testClass = EmployeeDaoImplTest, testInstance = com.websystique .springmvc.dao.EmployeeDaoImplTest@4fc55da3, testMethod = findById@EmployeeDaoImplTest, testException = [null], mergedContextConfiguration = [MergedContextConfi guration@49dc008c testClass = EmployeeDaoImplTest, locations = '{}', classes = '{class com.websystique.springmvc.configuration.HibernateTestConfiguration}', con textInitializerClasses = '[]', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]] Jul 12, 2015 1:46:50 AM org.springframework.test.context.transaction.TransactionalTestExecutionListener startNewTransaction INFO: Began transaction (5) for test context [DefaultTestContext@50dcb4b5 testClass = EmployeeDaoImplTest, testInstance = com.websystique.springmvc.dao.Employee DaoImplTest@4fc55da3, testMethod = findEmployeeBySsn@EmployeeDaoImplTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@49dc0 08c testClass = EmployeeDaoImplTest, locations = '{}', classes = '{class com.websystique.springmvc.configuration.HibernateTestConfiguration}', contextInitialize rClasses = '[]', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]]; transaction manager [org.springframework.orm.hibernate4.HibernateTransactionManager@aa80d36]; rollback [true] Jul 12, 2015 1:46:50 AM org.springframework.test.context.transaction.TransactionalTestExecutionListener endTransaction INFO: Rolled back transaction after test execution for test context [DefaultTestContext@50dcb4b5 testClass = EmployeeDaoImplTest, testInstance = com.websystique .springmvc.dao.EmployeeDaoImplTest@4fc55da3, testMethod = findEmployeeBySsn@EmployeeDaoImplTest, testException = [null], mergedContextConfiguration = [MergedCon textConfiguration@49dc008c testClass = EmployeeDaoImplTest, locations = '{}', classes = '{class com.websystique.springmvc.configuration.HibernateTestConfigurati on}', contextInitializerClasses = '[]', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]] Jul 12, 2015 1:46:50 AM org.springframework.test.context.transaction.TransactionalTestExecutionListener startNewTransaction INFO: Began transaction (6) for test context [DefaultTestContext@50dcb4b5 testClass = EmployeeDaoImplTest, testInstance = com.websystique.springmvc.dao.Employee DaoImplTest@4fc55da3, testMethod = saveEmployee@EmployeeDaoImplTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@49dc008c t estClass = EmployeeDaoImplTest, locations = '{}', classes = '{class com.websystique.springmvc.configuration.HibernateTestConfiguration}', contextInitializerClas ses = '[]', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]]; transaction manag er [org.springframework.orm.hibernate4.HibernateTransactionManager@aa80d36]; rollback [true] Jul 12, 2015 1:46:50 AM org.springframework.test.context.transaction.TransactionalTestExecutionListener endTransaction INFO: Rolled back transaction after test execution for test context [DefaultTestContext@50dcb4b5 testClass = EmployeeDaoImplTest, testInstance = com.websystique .springmvc.dao.EmployeeDaoImplTest@4fc55da3, testMethod = saveEmployee@EmployeeDaoImplTest, testException = [null], mergedContextConfiguration = [MergedContextC onfiguration@49dc008c testClass = EmployeeDaoImplTest, locations = '{}', classes = '{class com.websystique.springmvc.configuration.HibernateTestConfiguration}', contextInitializerClasses = '[]', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [nul l]]] Tests run: 23, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.982 sec Results : Tests run: 23, Failures: 0, Errors: 0, Skipped: 0 [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 9.481s [INFO] Finished at: Sun Jul 12 01:46:50 CEST 2015 [INFO] Final Memory: 17M/224M [INFO] ------------------------------------------------------------------------
That’s it.
References
If you like tutorials on this site, why not take a step further and connect me on Facebook , Google Plus & Twitter as well? I would love to hear your thoughts on these articles, it will help improve further our learning process.
In this post we will be developing a full-blown CRUD application using Spring Boot, AngularJS, Spring Data, JPA/Hibernate and MySQL,…
Spring Boot complements Spring REST support by providing default dependencies/converters out of the box. Writing RESTful services in Spring Boot…
Being able to start the application as standalone jar is great, but sometimes it might not be possible to run…
Spring framework has taken the software development industry by storm. Dependency Injection, rock solid MVC framework, Transaction management, messaging support,…
Let's secure our Spring REST API using OAuth2 this time, a simple guide showing what is required to secure a…
This post shows how an AngularJS application can consume a REST API which is secured with Basic authentication using Spring…
View Comments
"websystique" where have you been all my life ?!! :P
Hello,
First of all thank you very much for the code and explanations. I have a therotical question. I am new to testing. My question is when we test controllers don't we have to test that they add correct messages to the result object. Isn't it a side effect that should be tested?
Thanks in advance
Hi, It's a good approach to test those message as well [instead of mocking them], keeping everything intact in one place. There are times, however, when you are using a service in controller but you really want to test your controller [and not the details of service], mock is the better option there.
Hi,
I have an error, don't org.dbunit.dataset.NoSuchTableException: unv_users
My HibernateTestConfiguration is:
@Configuration
@EnableTransactionManagement
@ComponentScan({ "com.sfw.core.dao" })
public class HibernateTestConfiguration {
@Autowired
private Environment environment;
@Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan(new String[] { "com.sfw.core.model" });
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
@Bean(name = "dataSource")
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}
private Properties hibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
properties.put("hibernate.hbm2ddl.auto", "create");
properties.put("hibernate.cache.region.factory_class", "org.hibernate.cache.EhCacheRegionFactory");
return properties;
}
@Bean
@Autowired
public HibernateTransactionManager transactionManager(SessionFactory s) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(s);
return txManager;
}
}
My EntityDAOTest is:
@ContextConfiguration(classes = { HibernateTestConfiguration.class })
public abstract class EntityDAOTest extends AbstractTransactionalTestNGSpringContextTests {
@Autowired
DataSource dataSource;
@BeforeMethod
public void setUp() throws Exception {
IDatabaseConnection dbConn = new DatabaseDataSourceConnection(dataSource);
DatabaseOperation.CLEAN_INSERT.execute(dbConn, getDataSet());
}
protected abstract IDataSet getDataSet() throws Exception;
}
My H2GroupDAOTest is:
public class H2GroupDAOTest extends EntityDAOTest {
@Autowired
IGroupDAO groupDAO;
@Autowired
IUserDAO userDAO;
@Override
protected IDataSet getDataSet() throws Exception{
IDataSet dataSet = new FlatXmlDataSet(this.getClass().getClassLoader().getResourceAsStream("test-db/users.xml"));
return dataSet;
}
@Test
public void getAllTest() {
List UserList = userDAO.getAll();
}
The users.xml ils:
What am I doing wrong?
Hi I don't see any error as such. Do you have your test-db inside src/test/resources ? BTW, your test is not doing anything for the moment, you should use some Assertion.
Description Resource Path Location Type
Cannot change version of project facet Dynamic Web Module to 3.1. SpringHibernateExample line 1 Maven Java EE Configuration Problem
For example, if I modify listEmployees function in controller become following:
public String listEmployees (ModelMap model) {
List employees= employyService.findAllEmployees();();
model.addAttribute("employees", employees);
model.addAttribute("loggedinuser", getPrincipal());
return "allemployees";
}
and the getPrincipal() code is following:
private String getPrincipal(){
String userName = null;
Object principal = getCurrentUser();
if (principal instanceof UserDetails) {
userName = ((UserDetails)principal).getUsername();
} else {
userName = principal.toString();
}
return userName;
}
private Object getCurrentUser(){
return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
Then how can I make the test passed? Because without "model.addAttribute("loggedinuser", getPrincipal());" it actually can make it passed, but if include this line, if will complain Null Pointer Exceptino due to maven cannot get current login user...
For those who interest the solution, look at this post: http://stackoverflow.com/questions/38516666/spring-mvc-test-controller-with-spring-security
Mock it. Anyway i saw the post did mention it.
Just great, thanks!
Hi, Thanks for the nice comments.
I have an error in runtime of the test, the next line:
DatabaseOperation.CLEAN_INSERT.execute(dbConn, getDataSet());
Error contents:
org.dbunit.dataset.NoSuchTableException: user
Contents of variables during debugging are specified in the attached image.
I so think that it isn't possible to compare data from the user.xml and a h2db.
More information can be found in my question on stackoverflow: http://stackoverflow.com/questions/36120739/spring-hibernate-testng-and-h2-org-dbunit-dataset-nosuchtableexception-user
Please help me to solve this problem.
thanks for your efforts!
i think i can advice you some improvement...
It seems to me this code is wrong: (Boolean)model.get()
may be would better to do so: Boolean.valueOf((String) model.get() ?
Hi Nick,
Your comments are appreciated. If you feel something can be improved, please feel free to mention it , i will adapt the post.
thanks