TestNG

TestNG Spring Integration Example

In this article, we will go through a few examples of TestNG and spring integration. Before we start with the examples, let me first brief you on the goal of spring’s integration testing framework:

  •  Manage Spring IoC container caching between test execution.
  •  Provide Dependency Injection of test fixture instances.
  • Provide transaction management appropriate to integration testing.

In this article, first I will brief you on Spring TestContext Framework and then I will be showing an example for each of the above case using TestNG as the testing framework.

Below are my setup details:

  • I am using Eclipse as the IDE, version Luna 4.4.1.
  • I will be running the tests using eclipse TestNG plugin so you need to install the TestNG Eclipse Plugin.
  • Since the project depends on spring, TestNG, MySql for the database, we will be creating a Maven based project in eclipse. If you are new to Maven, you may go through the details here.

1. Spring TestContext Framework

In Spring 2.5 and later, unit and integration testing support is provided in the form of the annotation-driven Spring TestContext Framework. The TestContext framework is agnostic of the actual testing framework in use so irrespective of the framework, on each test execution event, the task to be performed is delegated to corresponding method in TestContextManager. That in turn takes care of loading and accessing ApplicationContext, caching it,  dependency injection of test instances and transactional execution of test methods, etc.

TestNG events delegated to  TestContextManager
TestNG events delegated to TestContextManager

 

Even though TestContextManager takes care of the test integration responsibility, there is still a layer that binds the testing framework to the spring’s integration testing framework and this layer consists of a couple of abstract support classes.

  1. AbstractTestNGSpringContextTests – Integrates the Spring TestContext Framework with explicit ApplicationContext which is accessible to sub-classes as protected member.
  2. AbstractTransactionalTestNGSpringContextTests – This extends AbstractTestNGSpringContextTests. It not only provides transactional support but also has some convenience functionality for JDBC access.

2. Add TestNG and Spring Dependencies to pom.xml

Our project is dependent on the below modules:

  1. spring-context – we will be loading ApplicationContext.
  2. spring-test – to access spring’s testing framework.
  3. spring-jdbc – for JdbcTemplate and DataSource support.
  4. mysql-connector-java – MySql driver.
  5. testng – as this is our testing tool.

With the above dependencies, our pom.xml looks like below:

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.javacodegeeks.testng.spring</groupId>
	<artifactId>testNGSpring</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>${spring.version}</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.26</version>
		</dependency>
		<dependency>
			<groupId>org.testng</groupId>
			<artifactId>testng</artifactId>
			<version>6.8.8</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<properties>
		<spring.version>4.1.5.RELEASE</spring.version>
	</properties>
</project>

3. Example of TestNG and Spring Dependency Injection

In this example, we will test our simple Foo bean. We will inject this bean into our test instance and then verify its value.

Foo:

package com.javacodegeeks.testng.spring;

public class Foo {
	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}	
}

Now define the bean in a spring XML context file and inject the name value using the property setter.

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: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">
	<bean id="foo" class="com.javacodegeeks.testng.spring.Foo">
		<property name="name" value="TestNG Spring"/>
	</bean>
</beans>

The test instance goes through the spring machinery for the dependency injection and the initialization of the the test instance. The factory callbacks such as setBeanName, setBeanFactory and the bean post processors are applied on the test instance.

  • Now lets look into our test class. First thing we notice is that the test class extends AbstractTestNGSpringContextTests as our goal is also to make sure that the dependencies are injected into our test instance.
  • Context file is provided using the type annotation @ContextConfiguration with file name context.xml as the value. Spring will use this file location to load the context which is then set to the protected member applicationContext
  • Bean foo is injected using @Autowired annotation.
  • The test class contains an @Autowired beanFactory member. Spring automatically injects BeanFactory bean into it.

About test cases:

  1. verifyFooName – verifies that the foo bean is injected and its name is same as the one set in the context file.
  2. verifyBeanFactory – verifies that the bean factory is injected.

SpringTestNGDependencyInjectionExample:

package com.javacodegeeks.testng.spring;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import static org.testng.Assert.*;
import org.testng.annotations.Test;

@ContextConfiguration("context.xml")
public class SpringTestNGDependencyInjectionExample extends AbstractTestNGSpringContextTests {
    @Test
    public void verifyFooName() {
    	System.out.println("verifyFooName: Is foo not null? " + (foo != null));
    	assertNotNull(foo);
    	System.out.println("verifyFooName: Foo name is '" + foo.getName() + "'");
        assertEquals(foo.getName(), "TestNG Spring");        
    }
    
    @Test
    public void verifyBeanFactory() {
    	System.out.println("verifyBeanFactory: Is bean factory not null? " + (beanFactory!= null)); 
        assertNotNull(beanFactory);
    }        
    
    @Autowired
    private BeanFactory beanFactory;    
    
    @Autowired
    private Foo foo;
}

testng_context_dependency_injection.xml:

<?xml version="1.0" encoding="UTF-8"?>
<suite name="TestNGSpringIntegration Suite" parallel="false">
  <test name="TestNGSpringIntegrationTest">
    <classes>
      <class name="com.javacodegeeks.testng.spring.SpringTestNGDependencyInjectionExample"/>
    </classes>
  </test>  
</suite>

Output:

[TestNG] Running:
  C:\javacodegeeks_ws\testNGSpring\src\test\resources\com\javacodegeeks\testng\spring\testng_context_dependency_injection.xml
verifyBeanFactory: Is bean factory not null? true
verifyFooName: Is foo not null? true
verifyFooName: Foo name is 'TestNG Spring'

===============================================
TestNGSpringIntegration Suite
Total tests run: 2, Failures: 0, Skips: 0
===============================================

4. Example of TestNG and Spring Context

Spring loads the context and caches it by default so that once loaded the test methods can directly access it from cache. This will certainly improve performance if the beans being loaded take time to initialize.

In this example, we will examine how one can override the default behavior by using the @DirtiesContext. By default, spring caches the context, so once a test method finishes its execution, the next test method to be run uses the same context as the one before it. We can override this behavior using @DirtiesContext. This will mark the cache as dirty and the context will be created and re-cached before the execution of the next test method.

About the methods used in the test class:

  1. saveFooName – This is a @BeforeClass method. Here we will save the foo’s name so that we can compare it later.
  2. removeFromCache – This method is annotated with @DirtiesContext so the context in cache gets marked as dirty. Also note that the foo’s name is changed here.
  3. verifyContextNew – This depends on removeFromCache so after its run, we check whether the foo’s name is still the default one or changed. Since removeFromCache is annotated with @DirtiesContext, verifyContextNew should be getting a new context, so the foo bean will be the refreshed one which is why its name is still the default one set in the context file.
  4. verifyContextSame – This method makes sure that the context is still the cached one.

SpringTestNGContextCacheExample:

package com.javacodegeeks.testng.spring;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import static org.testng.Assert.*;

@ContextConfiguration("context.xml")
public class SpringTestNGContextCacheExample extends AbstractTestNGSpringContextTests {

   @BeforeClass
    private void saveFooName() {
        TestNG_Spring= foo.getName();
        System.out.println("BeforeClass: foo name is '" + TestNG_Spring + "'");
        assertEquals(TestNG_Spring, "TestNG Spring");
    }

    @Test
    @DirtiesContext
    public void removeFromCache() {
        String newFooName = "New foo name";
        foo.setName(newFooName);
        System.out.println("removeFromCache: foo name changed to '" + foo.getName() + "'");
        this.dirtiedApplicationContext = super.applicationContext;
        System.out.println("removeFromCache: annotated @DirtiesContext so context will be marked for removal in afterMethod ");
    }

    @Test(dependsOnMethods = {"removeFromCache"})
    public void verifyContextNew() {
        System.out.println("verifyContextNew: is context re-cached? " + (dirtiedApplicationContext != applicationContext));
        System.out.println("verifyContextNew: foo name is '" + foo.getName() + "'");
        assertNotSame(super.applicationContext, this.dirtiedApplicationContext,
                "The application context should have been 'dirtied'.");
        assertEquals(foo.getName(), TestNG_Spring);
        this.dirtiedApplicationContext = super.applicationContext;
        foo.setName(MODIFIED_FOO_NAME);
        System.out.println("verifyContextNew: modify foo name to '" + MODIFIED_FOO_NAME + "'");
    }

    @Test(dependsOnMethods = { "verifyContextNew" })
    public void verifyContextSame() {
    	System.out.println("verifyContextSame: is context cached? " + (dirtiedApplicationContext == applicationContext));
        assertSame(this.applicationContext, this.dirtiedApplicationContext,
                "The application context should NOT have been 'dirtied'.");
        System.out.println("verifyContextSame: foo name is '" + foo.getName() + "'");
        assertEquals(foo.getName(), MODIFIED_FOO_NAME);
    }

    private String TestNG_Spring;
    private static final String MODIFIED_FOO_NAME = "TestNG Spring Name Changed";
    private ApplicationContext dirtiedApplicationContext;;

    @Autowired
    private Foo foo;
}

testng_context_cache.xml:

<?xml version="1.0" encoding="UTF-8"?>
<suite name="TestNGSpringIntegration Suite" parallel="false">
  <test name="TestNGSpringIntegrationTest">
    <classes>
      <class name="com.javacodegeeks.testng.spring.SpringTestNGContextCacheExample"/>
    </classes>
  </test>  
</suite>

Output:

[TestNG] Running:
  C:\javacodegeeks_ws\testNGSpring\src\test\resources\com\javacodegeeks\testng\spring\testng_context_cache.xml

BeforeClass: foo name is 'TestNG Spring'
removeFromCache: foo name changed to 'New foo name'
removeFromCache: annotated @DirtiesContext so context will be marked for removal in afterMethod 
verifyContextNew: is context re-cached? true
verifyContextNew: foo name is 'TestNG Spring'
verifyContextNew: modify foo name to 'TestNG Spring Name Changed'
verifyContextSame: is context cached? true
verifyContextSame: foo name is 'TestNG Spring Name Changed'

===============================================
TestNGSpringIntegration Suite
Total tests run: 3, Failures: 0, Skips: 0
===============================================

5. TestNG Spring Transaction Integration

In this example, we will run each test method within a transaction. By default, the transaction is rolled back after the test method’s execution. One can override this behavior or explicitly commit the transaction. To run the example, we will need the support of a database. We will be using MySql as the database. Below is the schema script that we will execute during the loading of the context and  so before we proceed with the first test method we will have the schema already built.

Script contains just an employee table with one column called name.

db-schema.sql:

drop table if exists `employee`;
CREATE TABLE employee (
  name VARCHAR(20) NOT NULL,
  PRIMARY KEY(name)
);

Some data which we will execute in the test class.

data.sql:

INSERT INTO employee VALUES('Joe');
INSERT INTO employee VALUES('Sam');

Some more additional data.

additional_data.sql:

INSERT INTO employee VALUES('John');

Here is our context file. Since we are going to run our test methods within a transaction, the context file contains inject dataSource and transactionManager beans. In jdbc:initialize-database, we initialize the schema.

tran_context.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd">

	<jdbc:initialize-database data-source="dataSource"
		enabled="true">
		<jdbc:script location="classpath:com/javacodegeeks/testng/spring/db-schema.sql" />
	</jdbc:initialize-database>

	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost/test" />
		<property name="username" value="root" />
		<property name="password" value="mnrpass" />
	</bean>

	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource" />
	</bean>	
	
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" autowire="byName"/>
</beans>

About our test class:

  1. The test class extends spring provided AbstractTransactionalTestNGSpringContextTests. This is important for the tests to automatically run within a transactional.
  2. saveMethodName – this is a @BeforeMethod configuration method to capture the method name which we will use later to run test based asserts.
  3. tran – assures that JdbcTemplate bean is injected.
  4. beforeTransaction – this is annotated with @BeforeTransaction. It executes before the start of transaction. We delete all the data from employee and re-create the data.
  5. insertEmployeeAndCommit – in this test method, we insert new employees and explicitly commit.
  6. insertEmployeeWithRollbackAsDefault – in this test method, we insert new employees. Since by default the transaction gets rolled back after the test’s execution, we shouldn’t see the new employees in @AfterTransaction method.
  7. insertEmployeeWithCommitAsDefault – here we override the default behavior of rollback using the annotation @Rollback(false) so now the transaction will automatically get committed.
  8. insertEmployeeUsingSqlAnnotation – here we run an Sql script using @Sql annotation. The script name additional_data.sql is passed as value.
  9. afterTransaction - this is annotated with @AfterTransaction. Here we run all the asserts to make sure we get the behavior we are expecting.

SpringTestNGTransactionExample:

package com.javacodegeeks.testng.spring;

import static org.springframework.test.context.transaction.TestTransaction.end;
import static org.springframework.test.context.transaction.TestTransaction.flagForCommit;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests;
import org.springframework.test.context.transaction.AfterTransaction;
import org.springframework.test.context.transaction.BeforeTransaction;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

@ContextConfiguration("tran_context.xml")
public class SpringTestNGTransactionExample extends	AbstractTransactionalTestNGSpringContextTests {
	private String method;

	@BeforeMethod
	public void saveMethodName(Method method) {
		this.method = method.getName();
	}

	@Test
	public void tran() {
		System.out.println("tran: verify JdbcTemplate is not null");
		assertNotNull(jdbcTemplate);
	}

	@BeforeTransaction
	public void beforeTransaction() {
		System.out.println("before transaction starts, delete all employees and re-run employee script");
		deleteFromTables("employee");
		executeSqlScript("classpath:/com/javacodegeeks/testng/spring/data.sql",	false);
	}

	@Test
	public void insertEmployeeAndCommit() {
		System.out.println("insertEmployeeAndCommit: insert employee 'Bill' and commit");
		String emp = "Bill";
		jdbcTemplate.update("insert into employee(name) values (?)", emp);
		assertEquals(countRowsInTableWhere("employee", "name='" + emp + "'"), 1);
		flagForCommit();
		end();
	}
	
	@Test
	public void insertEmployeeWithRollbackAsDefault() {
		System.out.println("insertEmployeeWithRollbackAsDefault: insert employee 'Bill', rollback by default");
		String emp = "Bill";
		jdbcTemplate.update("insert into employee(name) values (?)", emp);
		assertEquals(countRowsInTableWhere("employee", "name='" + emp + "'"), 1);
	}
	
	@Test
	@Rollback(false)
	public void insertEmployeeWithCommitAsDefault() {
		System.out.println("insertEmployeeWithCommitAsDefault: insert employee 'Bill', commit by default");
		String emp = "Bill";
		jdbcTemplate.update("insert into employee(name) values (?)", emp);
		assertEquals(countRowsInTableWhere("employee", "name='" + emp + "'"), 1);
	}
	
	@Test
	@Sql({"additional_data.sql"})
	public void insertEmployeeUsingSqlAnnotation() {
		System.out.println("insertEmployeeUsingSqlAnnotation: run additional sql using @sql annotation, rollback by default");
		assertEquals(countRowsInTableWhere("employee", "name='John'"), 1);
	}

	@AfterTransaction
	public void afterTransaction() {
		switch (method) {
		case "insertEmployeeAndCommit":
			assertEmployees("Bill", "Joe", "Sam");
			System.out.println("insertEmployeeAndCommit: employees found: 'Bill', 'Joe' and 'Sam'");
			break;
		case "insertEmployeeWithRollbackAsDefault":
			System.out.println("insertEmployeeWithRollbackAsDefault: employees found: 'Joe' and 'Sam'");
			assertEmployees("Joe", "Sam");
			break;
		case "insertEmployeeWithCommitAsDefault":
			System.out.println("insertEmployeeWithCommitAsDefault: employees found: 'Bill', 'Joe' and 'Sam'");
			assertEmployees("Bill", "Joe", "Sam");
			break;
		case "tran":
			break;
		case "insertEmployeeUsingSqlAnnotation":
			System.out.println("insertEmployeeWithRollbackAsDefault: employees found: 'Joe' and 'Sam'");
			assertEmployees("Joe", "Sam");
			break;
		default:
			throw new RuntimeException(
					"missing 'after transaction' assertion for test method: "
							+ method);
		}
	}

	private void assertEmployees(String... users) {
		List expected = Arrays.asList(users);
		Collections.sort(expected);
		List actual = jdbcTemplate.queryForList("select name from employee", String.class);
		Collections.sort(actual);
		System.out.println("Employees found: " + actual);
		assertEquals(expected, actual);
	}

	@Autowired
	private JdbcTemplate jdbcTemplate;
}

testng_spring_transaction.xml:

<?xml version="1.0" encoding="UTF-8"?>
<suite name="TestNGSpringIntegration Suite" parallel="false">
  <test name="TestNGSpringIntegrationTest">
    <classes>
      <class name="com.javacodegeeks.testng.spring.SpringTestNGTransactionExample"/>
    </classes>
  </test>  
</suite>

Output:

[TestNG] Running:
  C:\javacodegeeks_ws\testNGSpring\src\test\resources\com\javacodegeeks\testng\spring\testng_spring_transaction.xml

before transaction starts, delete all employees and re-run employee script
insertEmployeeAndCommit: insert employee 'Bill' and commit
Employees found: [Bill, Joe, Sam]
insertEmployeeAndCommit: employees found: 'Bill', 'Joe' and 'Sam'
before transaction starts, delete all employees and re-run employee script
insertEmployeeUsingSqlAnnotation: run additional sql using @sql annotation, rollback by default
insertEmployeeWithRollbackAsDefault: employees found: 'Joe' and 'Sam'
Employees found: [Joe, Sam]
before transaction starts, delete all employees and re-run employee script
insertEmployeeWithCommitAsDefault: insert employee 'Bill', commit by default
insertEmployeeWithCommitAsDefault: employees found: 'Bill', 'Joe' and 'Sam'
Employees found: [Bill, Joe, Sam]
before transaction starts, delete all employees and re-run employee script
insertEmployeeWithRollbackAsDefault: insert employee 'Bill', rollback by default
insertEmployeeWithRollbackAsDefault: employees found: 'Joe' and 'Sam'
Employees found: [Joe, Sam]
before transaction starts, delete all employees and re-run employee script

===============================================
TestNGSpringIntegration Suite
Total tests run: 5, Failures: 0, Skips: 0
===============================================

Download the Eclipse Project

In this article, I have shown you examples of TestNG Spring Integration.

Download
You can download the full source code of this example here: testNGSpring.zip

Ram Mokkapaty

Ram holds a master's degree in Machine Design from IT B.H.U. His expertise lies in test driven development and re-factoring. He is passionate about open source technologies and actively blogs on various java and open-source technologies like spring. He works as a principal Engineer in the logistics domain.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button