This is a small framework that allows you to do
unit testing on your POJOs under Spring environment. It is quite useful especially if you're adapting a Test-Driven Development methodoloy. This is Part 1 for testing service objects.
I will post a separate article in Part 2 for testing web pages and Spring Controller objects.
Only the following files are involved:
1. TestCase.java - the main class to extend for testing
2. AllTests.java - executes all test classes
3. LoginService.java - the class to be tested
4. LoginServiceTest.java - the test class for LoginService, contains all test cases
5. LoginServiceTestCase.java - the test case object for LoginService, executes test cases
6. applicationContext.xml - Spring configuration file
7. applicationContext-test.xml - Spring configuration file for test-specific beans
The package structure:
1. com.app.service.login - package for LoginService under /src folder
2. com.app.service.login - package for test classes for LoginService under /src-test folder, must be the same as the implementation class
3. com.test.main - package for test framework classes
Steps:
1. create a testcase object (LoginServiceTestCase) for a class to be tested (LoginService) by extending the TestCase.
2. create a test class (LoginServiceTest) which executes several test cases or scenarios - positive & negative test cases
3. modify AllTests class to include the LoginServiceTest class to be executed
4. modify applicationContext-test.xml to define the bean for LoginServiceTestCase
5. run the AllTests class as
JUnit
Sample output:
Test Case: Validate Login with valid credentials
PASS: Validate Login with valid credentials
Test Case: Validate Login with empty credentials
PASS: Validate Login with empty credentials
Test Case: Validate Login with invalid credentials
PASS: Validate Login with invalid credentials
The jar files you need in your classpath:
spring.jar
spring-webmvc.jar
spring-test.jar
junit-4.5.jar
commons-logging.jar
ojdbc14.jar
You can use this framework to test for business logic objects, Data Access Objects, and other POJOs.
If you're connected to a database, it will write to a database and rollback the transaction after every test case.
You can use it to test for several classes at the same time by simply following the steps above for each class.
You can also invoke this from your build script to automatically do a test before deployment. It will give you an idea whether to proceed with deployment depending on which test cases have failed or succeeded.
It also serves as a documentation for your test cases. Just use javadoc to generate APIs and attach the execution output or the JUnit screen shot.
This is a nice tool for Agile development.
Source Code Listing:
TestCase.java
=======================================================
package com.test.main;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
// This class represent the Test Case object in Unit testing
public abstract class TestCase {
private
String testName = "";
protected Map<String, Object> testConditions = new HashMap<String, Object>();
private Map<String, Boolean> expectedResults = new HashMap<String, Boolean>();
private Map<String, Boolean> actualResults = new HashMap<String, Boolean>();
public TestCase() {
}
// main execution point of every test case
public boolean isTestPass() throws Exception {
System.out.println("Test Case: " + this.getTestName());
this.executeTest();
this.checkActualResults();
this.syncResults();
boolean isMatch = this.isResultsMatch();
System.out.println((isMatch ? "PASS: " : "FAIL: ") + this.getTestName());
return isMatch;
}
// execute the method to be tested, set the parameters or conditions before execute
protected abstract void executeTest() throws Exception;
// check actual results and store in appropriate results values, use the setActualResult() method
protected abstract void checkActualResults() throws Exception;
// check all expected results againts actual results
private boolean isResultsMatch() {
Iterator i = expectedResults.entrySet().iterator();
Map.Entry<String,Boolean> result;
while (i.hasNext()) {
result = (Map.Entry<String,Boolean>) i.next();
if (this.getExpectedResult(result.getKey()) != this.getActualResult(result.getKey())) return false;
}
return true;
}
// this to ensure that all result entries are compared
private void syncResults() {
int exp = expectedResults.size();
int act = actualResults.size();
if (exp == act) return;
Map.Entry<String,Boolean> result;
if (exp > act) {
Iterator i = expectedResults.entrySet().iterator();
while (i.hasNext()) {
result = (Map.Entry<String,Boolean>) i.next();
if (! actualResults.containsKey( result.getKey() ) ) {
actualResults.put(result.getKey(), false);
}
}
} else {
Iterator i = actualResults.entrySet().iterator();
while (i.hasNext()) {
result = (Map.Entry<String,Boolean>) i.next();
if (! expectedResults.containsKey( result.getKey() ) ) {
expectedResults.put(result.getKey(), false);
}
}
}
}
// reset all test conditions, and test results
public void initialize() {
testConditions.clear();
expectedResults.clear();
actualResults.clear();
}
// set conditions to specific value
public void setAllConditions(Object value) {
this.setMapValue(this.testConditions, value);
}
// set expected results to specific value
public void setAllExpectedResults(boolean value) {
this.setMapValue(this.expectedResults, value);
}
private void setMapValue(Map m, Object value) {
Iterator i = m.entrySet().iterator();
Map.Entry<String,Object> entry;
while (i.hasNext()) {
entry = (Map.Entry<String,Object>) i.next();
m.put(entry.getKey(), value);
}
}
// setters & getters
public void setTestCondition(String key, Object value)
{testConditions.put(key, value);}
public Object getTestCondition(String key)
{return testConditions.get(key);}
public void setExpectedResult(String key, Boolean value)
{expectedResults.put(key, value);}
public Boolean getExpectedResult(String key)
{return expectedResults.get(key);}
public void setActualResult(String key, Boolean value)
{actualResults.put(key, value);}
public Boolean getActualResult(String key)
{return actualResults.get(key);}
public String getTestName()
{return testName;}
public void setTestName(String testName)
{this.testName = testName;}
}
AllTests.java
======================================================
package com.test.main;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import com.app.service.login.LoginServiceTest;
@RunWith(Suite.class)
@SuiteClasses({LoginServiceTest.class}) // add other test classes inside this annotation
public class AllTests {
}
LoginService.java
========================================================
package com.app.service.login;
public class LoginService {
public boolean isValid(String username, String password) {
// change the ff code with a call to DAO which digest password
if (username.equals("ludwin") && password.equals("xyz")) return true;
return false;
}
}
LoginServiceTest.java
=========================================================
package com.app.service.login;
import static org.junit.Assert.*;
import org.junit.After;
import org.junit.Before;
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.transaction.annotation.Transactional;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"file:C:/MyProject/src/applicationContext.xml", "file:C:/MyProject/src- test/applicationContext-test.xml"})
//@Transactional // enable if you're updating database during testing
public class LoginServiceTest {
@Autowired
private LoginServiceTestCase loginTestCase;
@Before
public void setup() {
loginTestCase.initialize();
}
@After
public void teardown() {
}
@Test
public void testLoginWithValidAccount() throws Exception {
loginTestCase.setTestName("Validate Login with valid credentials");
loginTestCase.setTestCondition(loginTestCase.CONDITION_USERNAME, "ludwin");
loginTestCase.setTestCondition(loginTestCase.CONDITION_PASSWORD, "xyz");
loginTestCase.setExpectedResult(loginTestCase.RESULT_IS_ERROR, false);
assertTrue(loginTestCase.isTestPass());
}
@Test
public void testLoginWithEmptyCredentials() throws Exception {
loginTestCase.setTestName("Validate Login with empty credentials");
loginTestCase.setTestCondition(loginTestCase.CONDITION_USERNAME, "");
loginTestCase.setTestCondition(loginTestCase.CONDITION_PASSWORD, "");
loginTestCase.setExpectedResult(loginTestCase.RESULT_IS_ERROR, true);
assertTrue(loginTestCase.isTestPass());
}
@Test
public void testLoginWithInvalidCredentials() throws Exception {
loginTestCase.setTestName("Validate Login with invalid credentials");
loginTestCase.setTestCondition(loginTestCase.CONDITION_USERNAME, "someuser");
loginTestCase.setTestCondition(loginTestCase.CONDITION_PASSWORD, "somepassword");
loginTestCase.setExpectedResult(loginTestCase.RESULT_IS_ERROR, true);
assertTrue(loginTestCase.isTestPass());
}
}
LoginServiceTestCase.java
==========================================================
package com.app.service.login;
import org.springframework.beans.factory.annotation.Autowired;
import com.test.main.TestCase;
public class LoginServiceTestCase extends TestCase {
@Autowired
private LoginService loginService; // the class to be tested
// conditions
protected final String CONDITION_USERNAME = "userName";
protected final String CONDITION_PASSWORD = "password";
protected final String RESULT_IS_ERROR = "isError";
private boolean isError;
protected void checkActualResults() throws Exception {
setActualResult(RESULT_IS_ERROR, isError);
}
protected void executeTest() throws Exception {
try {
String userName = (String) getTestCondition(CONDITION_USERNAME);
String password = (String) getTestCondition(CONDITION_PASSWORD);
isError = ! loginService.isValid(userName, password); // call the method to be tested
} catch (Exception e) {
isError = true;
}
}
}
applicationContext.xml
=======================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="loginService" class="com.app.service.login.LoginService" />
</beans>
applicationContext-test.xml
=======================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- data source, enable if you're connecting to database for testing -->
<!-- <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">-->
<!-- <property name="driverClassName" value="(fill-in)"/>-->
<!-- <property name="url" value="(fill-in)"/>-->
<!-- <property name="username" value="(fill-in)"/>-->
<!-- <property name="password" value="(fill-in)"/>-->
<!-- </bean>-->
<!-- transaction manager, enable to rollback database after testing -->
<!-- <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">-->
<!-- <property name="dataSource" ref="dataSource"/>-->
<!-- </bean>-->
<!-- test beans -->
<bean id="loginServiceTestCase" class="com.app.service.login.LoginServiceTestCase"/>
</beans>