Posts Tagged ‘junit

25
Mar
16

JPA initializing reference data

This page describes the process of initializing HSQLDB database located within a OpenEBJ container with reference data from a CSV file for testing purposes.

Full downloadable source for this page is available here. Corrections and enhancements are welcome, fork, change and push back to GitHub.

Software Stack

  1. Java 8
  2. Java EE 6
  3. JPA 2
  4. OpenEBJ 4.7

Pure JPA Approach

In the example below we will maintain user account information in a table capable of maintaining history.

 --------------------------
| User                     |
|--------------------------|
| id : varchar(320)        |
| valid_to_ts : datetime   |
|--------------------------|
| password : char(40)      |
| created_dt : datetime    |
| modified_dt : datetime   |
| valid_from_ts : datetime |
 --------------------------

Initial data

The initial data is stored in a csv file in a Tab delimited format.

We read the csv file in UserDataManager.java and call the updateTemporal() method of the inherited method of the HistoricalCrudDataManager parent class.

src/main/java/org/test/UserDataManager.java

package org.test;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Random;
import java.util.stream.Stream;

import javax.persistence.TypedQuery;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class UserDataManager extends HistoricalCrudDataManagerImpl<User, Serializable> {

	private static final Logger logger = LoggerFactory.getLogger(UserDataManager.class);
		
	/**
	 * Load data from a TAB delimited file named $HOME/data/User.csv.
	 * If record does not exist then create it. If it exists then update.
	 * 
	 * Note that tab delimited file not  have more than 1 record per new line.
	 * 
	 * @param dbName
	 */
	public void initData() throws IOException { 
		final String s = File.separator;
		final String home = System.getProperty("user.home");
		String path = home + s + "data" + s + "User.csv";
		File dataFile = new File(path);
		if(!dataFile.canRead()) {
			logger.info("file not found: " + path);
			return;
		}
		logger.info("reading records from: " + path);
		
		try (Stream<String> lines = Files.lines(dataFile.toPath(), StandardCharsets.UTF_8)) {
			lines.forEachOrdered(line -> {
				String[] cols = line.split("\\t");
				User u = new User();
				u.setId(cols[0]);
				u.setPassword(cols[1]);
				u.setValidToTs(END_TS);
				logger.info("loaded from file user: " + u);		
				updateTemporal(u, new UserPK(u.getId(), u.getValidToTs()));
			});
		}
	}
	
	public Long getUserCount() {
		TypedQuery<Long> q = entityManager.createNamedQuery("getCount", Long.class);
		q.setParameter("end_ts", END_TS);
		return q.getSingleResult();
	}
	
	public User getRandom() {
        Long count = getUserCount();
        Long random = getRandomNumberInRange(0, count-1);
        TypedQuery<User> q = entityManager.createNamedQuery("getAll", User.class);
        q.setParameter("end_ts", END_TS);
        User emp = q.setFirstResult(random.intValue())
        		.setMaxResults(1)
        		.getSingleResult();
        
		return emp;
	}
	
	private static Long getRandomNumberInRange(long min, long max) {
		Random r = new Random();
		return r.longs(min, (max + 1)).findFirst().getAsLong();
	}
}

The updateTemporal() method inserts a new record if it does not exist on the user table. If it exists it archives the old record before inserting.

src/main/java/org/test/HistoricalCrudDataManagerImpl.java

package org.test;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.Date;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HistoricalCrudDataManagerImpl<T extends Historical, PK extends Serializable> implements HistoricalCrudDataManager<T, PK> {
	private static final Logger logger = LoggerFactory.getLogger(HistoricalCrudDataManagerImpl.class);
	
	protected Class<T> entityClass;

	@PersistenceContext
	protected EntityManager entityManager;

	@SuppressWarnings("unchecked")
	public HistoricalCrudDataManagerImpl() {
		ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
		this.entityClass = (Class<T>) genericSuperclass.getActualTypeArguments()[0];
	}

	@Override
	public T create(T t) {
		if(t.getTimestamps() != null && t.getTimestamps().getValidFromTs()==null)
			t.getTimestamps().setValidFromTs(new Date());
		this.entityManager.persist(t);
		return t;
	}

	@Override
	public T read(PK id) {
		return this.entityManager.find(entityClass, id);
	}

	@Override
	public T updateTemporal(T t, PK id) {
		Date now = new Date();
		T tFromDb = this.entityManager.find(entityClass, id);
		if(tFromDb!=null) {
			if(t.sameAs(tFromDb)) {
				logger.info("Object same as: " + tFromDb);
				return tFromDb;
			} else {
				logger.info("Saving updated temporal object: " + tFromDb);
			}
			t.getTimestamps().setCreateTs(tFromDb.getTimestamps().getCreateTs());
			entityManager.remove(tFromDb);
			entityManager.flush();
			entityManager.detach(tFromDb);
			tFromDb.setValidToTs(now);
			entityManager.persist(tFromDb);
		}
		t.getTimestamps().setUpdateTs(now);
		t.getTimestamps().setValidFromTs(now);
		t.setValidToTs(END_TS);
		return create(t);
	}
	
	@Override
	public void delete(T t) {
		t = this.entityManager.merge(t);
		this.entityManager.remove(t);
	}
}

Finally put the following file into your $HOME/data directory. IMPORTANT: convert the spaces to TAB character between the fields. The second column is a sha1 hash of the username in the email address. example “smith”

$HOME/data/User.csv

smith@test.org	2b5c240e6abd88e71ffc225b0459016e4cba9bda
allen@test.org	a4aed34f4966dc8688b8e67046bf8b276626e284
ward@test.org	f7d6f8166205aa91930091f422c0634df8a7ceb4
jones@test.org	4c46bc790ffe655a1e65acfacf95da50cd4d3902

Test Case

The following Junit test case exercises the code. See the testInitData() test is where the interesting things happen. Results get saved into your $HOME/data and $HOME/logs directories.

src/test/java/org/test/UserManagerServiceTest.java

package org.test;

import javax.ejb.embeddable.EJBContainer;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

public class UserManagerServiceTest {
	private static EJBContainer container;
	private static UserManagerService userManagerService;
	
	@BeforeClass
	public static void beforeClass() throws Exception {
		// the following represents a db file in the users $HOME/data directory
	    container = ContainerProducer.produceContainer("usermanager_test1"); 
	    userManagerService = (UserManagerService) container.getContext().lookup("java:global/JPATemporalDB/UserManagerService");
	}
	
	@AfterClass
	public static void afterClass() throws Exception {
		if(container!=null)
			container.close();
	}

	/**
	 * Use this method to cleanup and initialize the data before each test method invocation.
	 */
	@Before
	public void before() {}

	/**
	 * Use this method to cleanup the data after each test method invocation.
	 */
	@After
	public void after() {}
	
	@Test
	public void testInitData() throws Exception {
		userManagerService.initData();
	}
	@Test
	public void testAddUser() throws Exception {
	    String id = "user" + System.currentTimeMillis();
	    String password = "";
	    long userCount = userManagerService.getUserCount();
		userManagerService.addUser(new User(id, password));
		long userCount2 = userManagerService.getUserCount();
		Assert.assertTrue(userCount2 > userCount);
	}
	
	@Test
	public void testUpdateUser() throws Exception {
		User randomUser = userManagerService.getRandomUser();
		String newPw = ""+System.currentTimeMillis();
		//String newPw = randomUser.getPassword(); // test with no change in password by uncommenting this line.
		randomUser.setPassword(newPw);
		userManagerService.updateUser(randomUser);
		User updatedUser = userManagerService.getUser(new UserPK(randomUser.getId()));
		Assert.assertEquals(newPw, updatedUser.getPassword());
	}
	
	@Test
	public void testReadUserById() throws Exception {
		User randomUser = userManagerService.getRandomUser();
		UserPK userPK = new UserPK(randomUser.getId(), HistoricalCrudDataManagerImpl.END_TS);
		User user = userManagerService.getUser(userPK);
		Assert.assertNotNull(user);
		System.out.println(user);
	}
}

Maven Configuration

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>org.test</groupId>
	<artifactId>JPATemporalDB</artifactId>
	<version>20160325</version>
	<packaging>jar</packaging>

	<name>UserManager</name>
	<pluginRepositories>
		<pluginRepository> <!-- Ignore this repository. Its only used for document publication. -->
			<id>numberformat-releases</id>
			<url>https://raw.githubusercontent.com/numberformat/wordpress/master/20130213/repo/</url>
		</pluginRepository>
	</pluginRepositories>


	<dependencies>
		<!-- Logging -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.1</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.1</version>
		</dependency>
		<!-- Unit Testing -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.openejb</groupId>
			<artifactId>openejb-core</artifactId>
			<version>4.7.1</version>
			<scope>test</scope>
		</dependency>

		<!-- APIs for services provided by the containers (order counts) this dependency must after openejb-core -->
		<dependency>
			<groupId>javax</groupId>
			<artifactId>javaee-web-api</artifactId>
			<version>6.0</version>
			<scope>provided</scope>
		</dependency>
		
		
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			<plugin> <!-- Ignore this plugin. Its only used for document publication. -->
				<groupId>github.numberformat</groupId>
				<artifactId>blog-plugin</artifactId>
				<version>1.0-SNAPSHOT</version>
				<configuration>
					<gitUrl>https://github.com/numberformat/wordpress/tree/master/${project.version}/${project.artifactId}</gitUrl>
				</configuration>
				<executions>
					<execution>
						<id>1</id>
						<phase>site</phase>
						<goals>
							<goal>generate</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>

Run the test case

The test case should run with no problems.

Database will go to the users $HOME/data directory. Logs will go to $HOME/logs.

Advertisements
20
Mar
16

Temporal Tables using JPA 2

This page is about storing and fetching data into tables that keep history using JPA 2. The method of implementing a relational database table and JPA code to allow storage of temporal information. This page uses a built-in HSQLDB database that outputs to a text file in the users home directory. No database configuration is needed.

Full downloadable source for this page is available here. Corrections and enhancements are welcome, fork, change and push back to GitHub.

Software Stack

  1. Java 8
  2. Java EE 6
  3. JPA 2
  4. OpenEBJ 4.7

Pure JPA Approach

In the example below we will maintain user account information. Since both id and valid_to_ts is in the primary key we can store multiple records for the same user id each with its own valid_to time stamp. The current valid record is indicated with a valid_to_ts as ‘9999-12-31’.

To expire the existing record we update the value to current time stamp and insert a new record with time stamp that expires in the future ex. ‘9999-12-31’. The data managers will perform the update and insert operation within the context of a transaction.

 --------------------------
| User                     |
|--------------------------|
| id : varchar(320)        |
| valid_to_ts : datetime   |
|--------------------------|
| password : char(40)      |
| created_dt : datetime    |
| modified_dt : datetime   |
| valid_from_ts : datetime |
 --------------------------

Advantages:
All current and historical information about a Entity is on a single table.

Disadvantages:
Because the “valid_to_ts” column will be update to reflect the time record was updated, you will not be able to use foreign key relations for these temporal tables.

A word about time zones and MAX dates

The valid_to_ts needs to have a time component since we need to record the time the record was expired and replaced with a new record.

The java.util.Date value represents the number of mili-seconds since January, 1 1970 midnight UTC. To indicate a end date of 9999-12-31 UTC the value is: 253402214400000. JDBC stores time stamps based on the time zone JVM is in. For example try to store the value of java.util.Date(0) into the database. On a JVM and database located in the “America/New_York” time zone the column value is stored with 1969-12-31 19:00:00.000000. This is (UTC-5). This is a problem because a JVM located in a different time zone like UTC will read the value literally as 1969-12-31 19:00:00.000000 which is wrong.

Solutions:

  1. Accept the fact that dates stored in the database are from a defined time zone like America/New_York and only use JVMs configured to that time zone. For example in your startup code put the following line: TimeZone.setDefault(TimeZone.getTimeZone(“America/New_York”)); In New York, Don’t use EDT or EST since those change from summer to winter.
  2. Store the time stamps as long. Use this approach if your JVMs run from different locations than your database. Its easy to convert and compare equality

For the purpose of this page we will assume the JVM and database are in the same time zone (America/New_York) and the “END” time stamp for current records is ‘9999-12-31 00:00:00’ local time which is (10000-1-1 05:00:00 UTC).

It’s useful to define the END_TS as a final constant somewhere in your code-base. This will save you the effort of creating an object that represents this value using java Calendar etc…

	public static final Date END_TS = new Date(253402214400000L);

Test Case

The following Junit test case exercises the code. See the testAddUser() and testUpdateUser() tests is where the interesting things happen. Results get saved into your $HOME/data and $HOME/logs directories.

src/test/java/org/test/UserManagerServiceTest.java

package org.test;

import javax.ejb.embeddable.EJBContainer;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

public class UserManagerServiceTest {
	private static EJBContainer container;
	private static UserManagerService userManagerService;
	
	@BeforeClass
	public static void beforeClass() throws Exception {
		// the following represents a db file in the users $HOME/data directory
	    container = ContainerProducer.produceContainer("usermanager_test1"); 
	    userManagerService = (UserManagerService) container.getContext().lookup("java:global/JPATemporalDB/UserManagerService");
	}
	
	@AfterClass
	public static void afterClass() throws Exception {
		if(container!=null)
			container.close();
	}

	/**
	 * Use this method to cleanup and initialize the data before each test method invocation.
	 */
	@Before
	public void before() {}

	/**
	 * Use this method to cleanup the data after each test method invocation.
	 */
	@After
	public void after() {}
	
	@Test
	public void testAddUser() throws Exception {
	    String id = "user" + System.currentTimeMillis();
	    String password = "";
	    long userCount = userManagerService.getUserCount();
		userManagerService.addUser(new User(id, password));
		long userCount2 = userManagerService.getUserCount();
		Assert.assertTrue(userCount2 > userCount);
	}
	
	@Test
	public void testUpdateUser() throws Exception {
		User randomUser = userManagerService.getRandomUser();
		String newPw = ""+System.currentTimeMillis();
		//String newPw = randomUser.getPassword(); // test with no change in password by uncommenting this line.
		randomUser.setPassword(newPw);
		userManagerService.updateUser(randomUser);
		User updatedUser = userManagerService.getUser(new UserPK(randomUser.getId()));
		Assert.assertEquals(newPw, updatedUser.getPassword());
	}
	
	@Test
	public void testReadUserById() throws Exception {
		User randomUser = userManagerService.getRandomUser();
		UserPK userPK = new UserPK(randomUser.getId(), HistoricalCrudDataManagerImpl.END_TS);
		User user = userManagerService.getUser(userPK);
		Assert.assertNotNull(user);
		System.out.println(user);
	}
}

Temporal Interface

The following interface should be placed on any entity that you want history on.

src/main/java/org/test/Historical.java

package org.test;

import java.util.Date;

/**
 * Used to get and set Temporal information from entity objects.
 */
public interface Historical {
	public Timestamps getTimestamps();
	public void setTimestamps(Timestamps timestamps);
	public Date getValidToTs();
	public void setValidToTs(Date validToTs);
	public boolean sameAs(Object obj);
}

HistoricalCrudDataManagerImpl

The following data manager base class has been modified to handle recording of history for each entity. Since the valid_to_ts appears in the primary key, In JPA 2 you can’t modify any part of the key directly. You need to query the entity, remove(), flush(), detach() it. Once detached you may update the PK and then persist(). An example of this is seen in the below data manager.

src/main/java/org/test/HistoricalCrudDataManagerImpl.java

package org.test;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.Date;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HistoricalCrudDataManagerImpl<T extends Historical, PK extends Serializable> implements HistoricalCrudDataManager<T, PK> {
	private static final Logger logger = LoggerFactory.getLogger(HistoricalCrudDataManagerImpl.class);
	
	protected Class<T> entityClass;

	@PersistenceContext
	protected EntityManager entityManager;

	@SuppressWarnings("unchecked")
	public HistoricalCrudDataManagerImpl() {
		ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
		this.entityClass = (Class<T>) genericSuperclass.getActualTypeArguments()[0];
	}

	@Override
	public T create(T t) {
		if(t.getTimestamps() != null && t.getTimestamps().getValidFromTs()==null)
			t.getTimestamps().setValidFromTs(new Date());
		this.entityManager.persist(t);
		return t;
	}

	@Override
	public T read(PK id) {
		return this.entityManager.find(entityClass, id);
	}

	@Override
	public T updateTemporal(T t, PK id) {
		Date now = new Date();
		T tFromDb = this.entityManager.find(entityClass, id);
		if(tFromDb!=null) {
			if(t.sameAs(tFromDb)) {
				logger.info("Object same as: " + tFromDb);
				return tFromDb;
			} else {
				logger.info("Saving updated temporal object: " + tFromDb);
			}
			t.getTimestamps().setCreateTs(tFromDb.getTimestamps().getCreateTs());
			entityManager.remove(tFromDb);
			entityManager.flush();
			entityManager.detach(tFromDb);
			tFromDb.setValidToTs(now);
			entityManager.persist(tFromDb);
		}
		t.getTimestamps().setUpdateTs(now);
		t.getTimestamps().setValidFromTs(now);
		t.setValidToTs(END_TS);
		return create(t);
	}
	
	@Override
	public void delete(T t) {
		t = this.entityManager.merge(t);
		this.entityManager.remove(t);
	}
}

User Entity

The user entity just needs to have the validToTs field since its part of the primary key. The rest of the time stamp values like update, created, validFrom time stamps are in a embedded object to make the entity class look a bit more cleaner.

src/main/java/org/test/User.java

package org.test;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
@NamedQueries({
	@NamedQuery(name="getCount", query = "SELECT COUNT(u) from User u where u.validToTs = :end_ts"),
	@NamedQuery(name="getAll", query = "SELECT u from User u where u.validToTs = :end_ts")
})
@IdClass(value=UserPK.class)
public class User implements Serializable, Historical {
	private static final long serialVersionUID = 1L;
	@Id
	private String id;

	@Id
	@Temporal(TemporalType.TIMESTAMP)
	@Column(name="VALID_TO_TS")
	private Date validToTs;

	@Column(length=40, nullable=false)
	private String password;
	
	@Embedded
	private Timestamps timestamps = new Timestamps();
	
	
	public Date getValidToTs() {
		return validToTs;
	}
	public void setValidToTs(Date validToTs) {
		this.validToTs = validToTs;
	}
	
	public User(){}
	public User(String id, String password) {
		this.id = id;
		this.password = password;
	}
	
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	
	@Override
	public String toString() {
		return "User [id=" + id + ", validToTs=" + validToTs + ", password=" + password + ", timestamps=" + timestamps
				+ "]";
	}
	public Timestamps getTimestamps() {
		return timestamps;
	}
	public void setTimestamps(Timestamps timestamps) {
		this.timestamps = timestamps;
	}
	@Override
	public boolean sameAs(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (!(obj instanceof User))
			return false;
		User other = (User) obj;
		if (getId() == null) {
			if (other.getId() != null)
				return false;
		} else if (!getId().equals(other.getId()))
			return false;
		if (getPassword() == null) {
			if (other.getPassword() != null)
				return false;
		} else if (!getPassword().equals(other.getPassword()))
			return false;
		return true;
	}
}

Nothing really interesting happening in the rest of the code. You may checkout the Git repository for this page if interested.

Maven Configuration

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>org.test</groupId>
	<artifactId>JPATemporalDB</artifactId>
	<version>20160320</version>
	<packaging>jar</packaging>

	<name>UserManager</name>
	<pluginRepositories>
		<pluginRepository> <!-- Ignore this repository. Its only used for document publication. -->
			<id>numberformat-releases</id>
			<url>https://raw.githubusercontent.com/numberformat/wordpress/master/20130213/repo/</url>
		</pluginRepository>
	</pluginRepositories>


	<dependencies>
		<!-- Logging -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.1</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.1</version>
		</dependency>
		<!-- Unit Testing -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.openejb</groupId>
			<artifactId>openejb-core</artifactId>
			<version>4.7.1</version>
			<scope>test</scope>
		</dependency>

		<!-- APIs for services provided by the containers (order counts) this dependency must after openejb-core -->
		<dependency>
			<groupId>javax</groupId>
			<artifactId>javaee-web-api</artifactId>
			<version>6.0</version>
			<scope>provided</scope>
		</dependency>
		
		
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			<plugin> <!-- Ignore this plugin. Its only used for document publication. -->
				<groupId>github.numberformat</groupId>
				<artifactId>blog-plugin</artifactId>
				<version>1.0-SNAPSHOT</version>
				<configuration>
					<gitUrl>https://github.com/numberformat/wordpress/tree/master/${project.version}/${project.artifactId}</gitUrl>
				</configuration>
				<executions>
					<execution>
						<id>1</id>
						<phase>site</phase>
						<goals>
							<goal>generate</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>

Run the test case

The test case should run with no problems.

Database will go to the users $HOME/data directory. Logs will go to $HOME/logs.

15
Mar
16

Simple Example of a Generic JPA CRUD DataManager

This page describes how to create a Generic JPA Data Manager for CRUD operations. The sample on this page is self contained. An in-memory database is included along with a JUnit test case. You can download and run this example from the github repository link below.

Full downloadable source for this page is available here. Corrections and enhancements are welcome, fork, change and push back to GitHub.

Background

CRUD operations are implemented by boilerplate code. You can simplify your data managers by extending a Generic DataManager that implements the CRUD operations.

The CRUD operations include:

  • Create
  • Read
  • Update
  • Delete

Software Stack

  1. Java 8
  2. Java EE 6
  3. JPA 2
  4. OpenEBJ 4.7

Test Case

The following is the main test case. It contains a reference to an EJB container that will be used to run the application. The produceContainer method accepts a name of a database. This will result in a file being created in the users $HOME/data/ directory. Each test case class can have a dedicated database instance. Once the test case is complete HSQL can write the results back to the file system or not. This functionality is customizable by passing properties in the connection URL for hsqldb.

src/test/java/org/test/UserManagerServiceTest.java

package org.test;

import javax.ejb.embeddable.EJBContainer;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

public class UserManagerServiceTest {
	private static EJBContainer container;
	private static UserManagerService userManagerService;
	
	@BeforeClass
	public static void beforeClass() throws Exception {
		// the following represents a db file in the users $HOME/data directory
	    container = ContainerProducer.produceContainer("usermanager_test1"); 
	    userManagerService = (UserManagerService) container.getContext().lookup("java:global/UserManagerApp/UserManagerService");
	}
	
	@AfterClass
	public static void afterClass() throws Exception {
		if(container!=null)
			container.close();
	}

	/**
	 * Use this method to cleanup and initialize the data before each test method invocation.
	 */
	@Before
	public void before() {}

	/**
	 * Use this method to cleanup the data after each test method invocation.
	 */
	@After
	public void after() {}
	
	@Test
	public void testAddUser() throws Exception {
	    long userCount = userManagerService.getUserCount();
	    String id = "user" + System.currentTimeMillis();
	    String password = "";
		userManagerService.addUser(new User(id, password));
		Long newUserCount = userManagerService.getUserCount();
		Assert.assertTrue(newUserCount > userCount);
	}
	
	@Test
	public void testReadUserById() throws Exception {
		User randomUser = userManagerService.getRandomUser();
		User user = userManagerService.getUser(randomUser.getId());
		Assert.assertNotNull(user);
	}
}

Container Producer

This is where the database connections are configured. The static method accepts a database name and saves an instance of the database into the users $HOME/data directory. The database is initialized with a completely blank schema. Once the test is complete the user can open the resulting database “.script” file and insert the mock data. Next time the test case runs it will load the data specified in the “.script” file. The JPA implementation will also check for any changes in the entity beans and propagate the DDL changes to the script file.

Since the scripts are regular files Using this method the developer has flexibility. For example the developer can have a complete library of script files one for each test case. The script files could also be checked into SVN or GIT version control so they can be shared or after finding a bug they could be restored to their original condition etc…

src/test/java/org/test/ContainerProducer.java

package org.test;

import java.util.HashMap;
import java.util.Map;

import javax.ejb.embeddable.EJBContainer;

public class ContainerProducer {

	public static final EJBContainer produceContainer(String dbName) {
	    // Create the container instance, passing it the properties map:
	    Map<String, Object> p = new HashMap<>();
	    String userHome = System.getProperty("user.home");
        p.put("DefaultDS", "new://Resource?type=DataSource");
        p.put("DefaultDS.JdbcUrl", "jdbc:hsqldb:file:"+userHome+"/data/"+dbName+";shutdown=true");

	    return javax.ejb.embeddable.EJBContainer.createEJBContainer(p);
	}
}

Generic CRUD JPA Data Managers

The following 2 files are the main focus of the article. Most of the boiler plate code for the crud operations have been implemented here. This leaves your datamanager free to implement any custom functionality.

src/main/java/org/test/CrudDataManager.java

package org.test;

import java.io.Serializable;

public interface CrudDataManager<T, PK extends Serializable> {
    T create(T t);
    T read(PK id);
    T update(T t);
    void delete(T t);
}

src/main/java/org/test/CrudDataManagerImpl.java

package org.test;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

public class CrudDataManagerImpl<T, PK extends Serializable> implements CrudDataManager<T, PK> {

	protected Class<T> entityClass;

	@PersistenceContext
	protected EntityManager entityManager;

	@SuppressWarnings("unchecked")
	public CrudDataManagerImpl() {
		ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
		this.entityClass = (Class<T>) genericSuperclass.getActualTypeArguments()[0];
	}

	@Override
	public T create(T t) {
		this.entityManager.persist(t);
		return t;
	}

	@Override
	public T read(PK id) {
		return this.entityManager.find(entityClass, id);
	}

	@Override
	public T update(T t) {
		return this.entityManager.merge(t);
	}

	@Override
	public void delete(T t) {
		t = this.entityManager.merge(t);
		this.entityManager.remove(t);
	}
}

Data Manager Layer

Embarrassingly simple data manager layer.

src/main/java/org/test/UserDataManager.java

package org.test;

import java.io.Serializable;
import java.util.Random;

public class UserDataManager extends CrudDataManagerImpl<User, Serializable> {

	public Long getUserCount() {
		return entityManager.createNamedQuery("getCount", Long.class).getSingleResult();
	}
	
	public User getRandom() {
        Long count = getUserCount();
        Long random = getRandomNumberInRange(0, count-1);

        User emp = entityManager.createNamedQuery("getAll", User.class).setFirstResult(random.intValue())
        		.setMaxResults(1)
        		.getSingleResult();
        
		return emp;
	}
	
	private static Long getRandomNumberInRange(long min, long max) {
		Random r = new Random();
		return r.longs(min, (max + 1)).findFirst().getAsLong();
	}
}

User Entity Class

The following represents a user object we will store to the database.

src/main/java/org/test/User.java

package org.test;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;

@Entity
@NamedQueries({
	@NamedQuery(name="getCount", query = "SELECT COUNT(u) from User u"),
	@NamedQuery(name="getAll", query = "SELECT u from User u")
})
public class User implements Serializable {
	@Id
	@Column(name="userId", length=320, nullable=false)
	private String id;
	@Column(length=40, nullable=false)
	private String password;
	
	public User(){}
	public User(String id, String password) {
		this.id = id;
		this.password = password;
	}
	
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	@Override
	public String toString() {
		return "User [id=" + id + ", password=" + password + "]";
	}
}

Service Layer

Embarrassingly simple service layer. Each method executes within a transaction automatically.

src/main/java/org/test/UserManagerService.java

package org.test;

import javax.ejb.Stateless;
import javax.inject.Inject;

import org.slf4j.Logger;

@Stateless
public class UserManagerService {
	@Inject
	private Logger logger;

	@Inject
	private UserDataManager userDataManager;

	public void addUser(User user) throws Exception {
		logger.info("inside adduser");
		userDataManager.create(user);
	}
	
	public User getUser(String id) throws Exception {
		User user = userDataManager.read(id);
		return user;
	}

	public User getRandomUser() throws Exception {
		return userDataManager.getRandom();
	}
	public Long getUserCount() {
		return userDataManager.getUserCount();
	}
}

Logger CDI injection

Just a simple way to inject a logger into the entity classes.
src/main/java/org/test/LoggerProducer.java

package org.test;
 
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.InjectionPoint;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LoggerProducer {
    @Produces
    public Logger produceLogger(InjectionPoint injectionPoint) {
    	// check log4j.xml to find out where logs are being written to.
    	return LoggerFactory.getLogger(injectionPoint.getMember().getDeclaringClass().getName());
    }
}

Project Configuration

src/main/resources/log4j.xml

<!DOCTYPE log4j:configuration PUBLIC
  "-//APACHE//DTD LOG4J 1.2//EN" "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
   
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    
	<appender name="file" class="org.apache.log4j.FileAppender">
	   <param name="file" value="${user.home}/logs/UserManager.log" />
	   <layout class="org.apache.log4j.PatternLayout">
		<param name="ConversionPattern" 
			value="%d %-5p %c:%L - %m%n" />
	   </layout>
	</appender>
	    
    <logger name="org.test">
        <level value="DEBUG"/>
    </logger>
    <logger name="org">
        <level value="DEBUG"/>
    </logger>
    <root>
        <level value="ERROR"/>
        <appender-ref ref="file"/>
    </root> 
</log4j:configuration>

The following file should in the META-INF/ directory. If its going to be part of a war it should be located in a jar file that is inside the WEB-INF/lib directory. Read your containers documentation on the proper location of this file if its not clear.

JPA Config

src/main/resources/META-INF/persistence.xml

<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_2_0.xsd"
             version="2.0">

	<persistence-unit name="UserManager">
		<provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
		<jta-data-source>DefaultDS</jta-data-source>

		<properties>
			<property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)" />
		</properties>
	</persistence-unit>
</persistence>

CDI config

Create a blank src/main/resources/META-INF/beans.xml file to enable Java contexts and dependency injection (CDI)

Maven Configuration

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>org.test</groupId>
	<artifactId>UserManagerApp</artifactId>
	<version>20160315</version>
	<packaging>jar</packaging>

	<name>UserManager</name>
	<pluginRepositories>
		<pluginRepository> <!-- Ignore this repository. Its only used for document publication. -->
			<id>numberformat-releases</id>
			<url>https://raw.githubusercontent.com/numberformat/wordpress/master/20130213/repo/</url>
		</pluginRepository>
	</pluginRepositories>


	<dependencies>
		<!-- Logging -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.1</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.1</version>
		</dependency>
		<!-- Unit Testing -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.openejb</groupId>
			<artifactId>openejb-core</artifactId>
			<version>4.7.1</version>
			<scope>test</scope>
		</dependency>

		<!-- APIs for services provided by the containers (order counts) this dependency must after openejb-core -->
		<dependency>
			<groupId>javax</groupId>
			<artifactId>javaee-web-api</artifactId>
			<version>6.0</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			<plugin> <!-- Ignore this plugin. Its only used for document publication. -->
				<groupId>github.numberformat</groupId>
				<artifactId>blog-plugin</artifactId>
				<version>1.0-SNAPSHOT</version>
				<configuration>
					<gitUrl>https://github.com/numberformat/wordpress/tree/master/${project.version}/${project.artifactId}</gitUrl>
				</configuration>
				<executions>
					<execution>
						<id>1</id>
						<phase>site</phase>
						<goals>
							<goal>generate</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>

Run the test case

The test case should run with no problems.

Database will go to the users $HOME/data directory. Logs will go to $HOME/logs.

13
Mar
16

Development and Unit testing of Java EE / JPA applications

This page describes how to create a working environment that allows you to develop, run and test a Java JPA application. Except for the logging API, this example contains no vendor specific code. No Spring framework, no Hibernate, it uses pure java annotations wherever possible. It will use the OpenEJB/OpenJPA to provide the runtime environment to JUnit for testing.

This page is influenced by the concept described in book “Domain Driven Design” by Eric J. Evans. The example will use Java Session beans transaction and service Layer, repository beans with JPA Entity managers for data access and CDI for dependency injection, finally HSQLDB will store the results to the file-system for later analysis.

Once complete and tested this components could be used as part of a larger enterprise application that runs in a full JEE container with little to no modifications.

Full downloadable source for this page is available here. Corrections and enhancements are welcome, fork, change and push back to GitHub.

Background

Java EE containers are large and clumsy to work with. They often times require you to package your application into WAR or EAR files and deploy them into a managed folder where they pick up and deploy internally within the container.

There is a need for a lighter weight solution. The solution should be quick to setup and run and allow for some level of JUnit testing.

Session beans on the other hand often times contain business logic that needs to be tested.

Mocking frameworks like JMock or Mockito allow to mock the data access layer. It is the preferred approach. The data is setup could be time consuming and awkward to setup in java code.

This page discusses a way to test the service and repository layer without mocking any classes, or hand coding of any table data in java code. Instead you can use JPA and HSQLDB to create and initialize a in-memory database.

Mocking frameworks can still be used to mock other aspects of the application that is not available to the service layer.

Software Stack

  1. Java 8
  2. Java EE 6
  3. JPA 2
  4. OpenEBJ 4.7

Test Case

The following is the main test case. It contains a reference to an EJB container that will be used to run the application. The produceContainer method accepts a name of a database. This will result in a file being created in the users $HOME/data/ directory. Each test case class can have a dedicated database instance. Once the test case is complete HSQL can write the results back to the file system or not. This functionality is customizable by passing properties in the connection URL for hsqldb.

src/test/java/org/test/UserManagerServiceTest.java

package org.test;

import javax.ejb.embeddable.EJBContainer;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

public class UserManagerServiceTest {
	private static EJBContainer container;
	
	@BeforeClass
	public static void beforeClass() throws Exception {
		// the following represents a db file in the users $HOME/data directory
	    container = ContainerProducer.produceContainer("usermanager_test1"); 
	}
	
	@AfterClass
	public static void afterClass() throws Exception {
		if(container!=null)
			container.close();
	}

	/**
	 * Use this method to cleanup and initialize the data before each test method invocation.
	 */
	@Before
	public void before() {}

	/**
	 * Use this method to cleanup the data after each test method invocation.
	 */
	@After
	public void after() {}
	
	@Test
	public void testAddUser() throws Exception {
	    UserManagerService userManagerService = (UserManagerService) container.getContext().lookup("java:global/UserManagerApp/UserManagerService");
	    int userCount = userManagerService.getUserCount();
	    String id = "user" + System.currentTimeMillis();
	    String name = "Test User" + System.currentTimeMillis();
	    String password = ""+System.currentTimeMillis();
		userManagerService.addUser(new User(id, name, password));
		int newUserCount = userManagerService.getUserCount();
		Assert.assertTrue(newUserCount > userCount);
	}
}

Container Producer

This is where the database connections are configured. The static method accepts a database name and saves an instance of the database into the users $HOME/data directory. The database is initialized with a completely blank schema. Once the test is complete the user can open the resulting database “.script” file and insert the mock data. Next time the test case runs it will load the data specified in the “.script” file. The JPA implementation will also check for any changes in the entity beans and propagate the DDL changes to the script file.

Since the scripts are regular files Using this method the developer has flexibility. For example the developer can have a complete library of script files one for each test case. The script files could also be checked into SVN or GIT version control so they can be shared or after finding a bug they could be restored to their original condition etc…

src/test/java/org/test/ContainerProducer.java

package org.test;

import java.util.HashMap;
import java.util.Map;

import javax.ejb.embeddable.EJBContainer;

public class ContainerProducer {

	public static final EJBContainer produceContainer(String dbName) {
	    // Create the container instance, passing it the properties map:
	    Map<String, Object> p = new HashMap<>();
	    String userHome = System.getProperty("user.home");
        p.put("DefaultDS", "new://Resource?type=DataSource");
        p.put("DefaultDS.JdbcUrl", "jdbc:hsqldb:file:"+userHome+"/data/"+dbName+";shutdown=true");

	    return javax.ejb.embeddable.EJBContainer.createEJBContainer(p);
	}
}

User Entity Class

The following represents a user object we will store to the database.

src/main/java/org/test/User.java

package org.test;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQuery;

@Entity
@NamedQuery(name="getCount", query = "SELECT COUNT(u) from User u")
public class User {
	@Id
	@Column(length=50, nullable=false)
	private String id;
	@Column(length=50, nullable=false)
	private String name;
	@Column(length=25, nullable=false)
	private String password;
	
	public User(){}
	public User(String id, String name, String password) {
		this.id = id;
		this.name = name;
		this.password = password;
	}
	
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}

Service Layer

Embarrassingly simple service layer.

src/main/java/org/test/UserManagerService.java

package org.test;

import javax.ejb.Stateless;
import javax.inject.Inject;

import org.slf4j.Logger;

@Stateless
public class UserManagerService {
	@Inject
	private Logger logger;

	@Inject
	private UserDataManager userDataManager;

	public void addUser(User user) throws Exception {
		logger.info("inside adduser");
		userDataManager.addUser(user);
	}

	public int getUserCount() {
		return userDataManager.getUserCount();
	}
}

Data Manager Layer

Embarrassingly simple data manager layer.

src/main/java/org/test/UserDataManager.java

package org.test;

import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.slf4j.Logger;

public class UserDataManager {
    @Inject
    private Logger logger;
    
	@PersistenceContext(unitName="UserManager")
	private EntityManager em;

	public void addUser(User user) {
		logger.info("inside addUser()");
		em.persist(user);
	}

	public int getUserCount() {
		return em.createNamedQuery("getCount", Long.class).getSingleResult().intValue();
	}
}

Logger CDI injection

Just a simple way to inject a logger into the entity classes.
src/main/java/org/test/LoggerProducer.java

package org.test;
 
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.InjectionPoint;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LoggerProducer {
    @Produces
    public Logger produceLogger(InjectionPoint injectionPoint) {
    	// check log4j.xml to find out where logs are being written to.
    	return LoggerFactory.getLogger(injectionPoint.getMember().getDeclaringClass().getName());
    }
}

Project Configuration

src/main/resources/log4j.xml

<!DOCTYPE log4j:configuration PUBLIC
  "-//APACHE//DTD LOG4J 1.2//EN" "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
   
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    
	<appender name="file" class="org.apache.log4j.FileAppender">
	   <param name="file" value="${user.home}/logs/UserManager.log" />
	   <layout class="org.apache.log4j.PatternLayout">
		<param name="ConversionPattern" 
			value="%d %-5p %c:%L - %m%n" />
	   </layout>
	</appender>
	    
    <logger name="org.test">
        <level value="DEBUG"/>
    </logger>
    <logger name="org">
        <level value="DEBUG"/>
    </logger>
    <root>
        <level value="ERROR"/>
        <appender-ref ref="file"/>
    </root> 
</log4j:configuration>

The following file should in the META-INF/ directory. If its going to be part of a war it should be located in a jar file that is inside the WEB-INF/lib directory. Read your containers documentation on the proper location of this file if its not clear.

JPA Config

src/main/resources/META-INF/persistence.xml

<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_2_0.xsd"
             version="2.0">

	<persistence-unit name="UserManager">
		<provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
		<jta-data-source>DefaultDS</jta-data-source>

		<properties>
			<property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)" />
		</properties>
	</persistence-unit>
</persistence>

CDI config

Create a blank src/main/resources/META-INF/beans.xml file to enable Java contexts and dependency injection (CDI)

Maven Configuration

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>org.test</groupId>
	<artifactId>UserManagerApp</artifactId>
	<version>20160313</version>
	<packaging>jar</packaging>

	<name>UserManager</name>
	<pluginRepositories>
		<pluginRepository> <!-- Ignore this repository. Its only used for document publication. -->
			<id>numberformat-releases</id>
			<url>https://raw.githubusercontent.com/numberformat/wordpress/master/20130213/repo/</url>
		</pluginRepository>
	</pluginRepositories>


	<dependencies>
		<!-- Logging -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.1</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.1</version>
		</dependency>
		<!-- Unit Testing -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.openejb</groupId>
			<artifactId>openejb-core</artifactId>
			<version>4.7.1</version>
			<scope>test</scope>
		</dependency>

		<!-- APIs for services provided by the containers -->
		<dependency>
			<groupId>javax</groupId>
			<artifactId>javaee-web-api</artifactId>
			<version>6.0</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			<plugin> <!-- Ignore this plugin. Its only used for document publication. -->
				<groupId>github.numberformat</groupId>
				<artifactId>blog-plugin</artifactId>
				<version>1.0-SNAPSHOT</version>
				<configuration>
					<gitUrl>https://github.com/numberformat/wordpress/tree/master/${project.version}/${project.artifactId}</gitUrl>
				</configuration>
				<executions>
					<execution>
						<id>1</id>
						<phase>site</phase>
						<goals>
							<goal>generate</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
			<!-- this configures the surefire plugin to run your tests with the javaagent 
				enabled -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-surefire-plugin</artifactId>
				<configuration>
					<forkMode>pertest</forkMode>
					<argLine>-javaagent:${project.basedir}/target/openejb-javaagent-4.6.0.jar</argLine>
					<workingDirectory>${project.basedir}/target</workingDirectory>
				</configuration>
			</plugin>
			<!-- this tells maven to copy the openejb-javaagent jar into your target/ 
				directory -->
			<!-- where surefire can see it 
			Eclipse JUnits can use the following VM arg: -javaagent:target/openejb-javaagent-4.6.0.jar -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-dependency-plugin</artifactId>
				<executions>
					<execution>
						<id>copy</id>
						<phase>process-resources</phase>
						<goals>
							<goal>copy</goal>
						</goals>
						<configuration>
							<artifactItems>
								<artifactItem>
									<groupId>org.apache.openejb</groupId>
									<artifactId>openejb-javaagent</artifactId>
									<version>4.6.0</version>
									<outputDirectory>${project.build.directory}</outputDirectory>
								</artifactItem>
							</artifactItems>
						</configuration>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>

Run the test case

The test case should run with no problems.

Database will go to the users $HOME/data directory. Logs will go to $HOME/logs.

11
Apr
10

Annotation based Equals and HashCode Test Case

This page will describe how you can annotate a Java bean so that JUnit verify proper equals() and hashCode() implementations.

Background

The fastest way to implement equals and hash code is to manually code it. The problem with doing it manually is that the code become out of sync.

Solution

Annotate the fields that should be present in the equals and hashCode() methods. JUnit checks these annotations and verifies that the equals() and hashCode() methods are implemented properly. You still need to manually update the equals and hashcode methods if something changes but now at least you have a test case that will fail if something goes wrong.

Requirements

  • Java 5 or better
  • Maven 2 – (Link to a Maven Tutorial available on right navigation)

Implementation

The following example will demonstrate the very basic usage scenario of annotation based unit testing of equals and hashCode methods.

First step is to create a project using maven archetype. Open up the command prompt and navigate to an empty directory.

mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart

groupId: com.test
artifactId: beanUnitTest

Answer the rest of the questions with defaults “Just hit the enter key”

If you are using eclipse then you may want to regenerate your project before importing it into eclipse.
do this by typing:

mvn eclipse:clean eclipse:eclipse

Next define the equals annotation. This annotation will be used to mark the fields that should participate in the equals and hashCode methods.

/src/test/java/com/test/Equals.java

package com.test;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Equals {
}

/src/main/java/com/test/TestBean.java

package com.test;

public class TestBean {
	@Equals
	private String name;
	@Equals
	private String address;
	@Equals
	private boolean drugTestPassed;
	@Equals
	private int age;
	@Equals
	private long birthday;
	@Equals
	private SubBean subBean = new SubBean();

// getters and setters go here...

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((address == null) ? 0 : address.hashCode());
		result = prime * result + age;
		result = prime * result + (int) (birthday ^ (birthday >>> 32));
		result = prime * result + (drugTestPassed ? 1231 : 1237);
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		result = prime * result + ((subBean == null) ? 0 : subBean.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		TestBean other = (TestBean) obj;
		if (address == null) {
			if (other.address != null)
				return false;
		} else if (!address.equals(other.address))
			return false;
		if (age != other.age)
			return false;
		if (birthday != other.birthday)
			return false;
		if (drugTestPassed != other.drugTestPassed)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		if (subBean == null) {
			if (other.subBean != null)
				return false;
		} else if (!subBean.equals(other.subBean))
			return false;

		return true;
	}
}

src/main/java/com/test/SubBean.java

package com.test;

/**
 * This is a sub bean where only the ID field is used to decide
 * if the object is equal or not.
 *
 */
public class SubBean {

	@Equals
	private Integer id;

	private String name;
	private String age;

// getters and setters...

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		SubBean other = (SubBean) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		return true;
	}
}

The following is the heart of the system. It is just an abstract base class that could be extended. It was taken from http://blog.coryfoy.com/2008/05/unit-testing-equals-and-hashcode-of-java-beans/ and modified to utilize the annotation features of Java 5. Also some changes were made to not traverse object attributes (see appendix below for more details).

/src/test/java/com/test/BeanTestCase.java

package com.test;
import junit.framework.TestCase;

import java.lang.reflect.Field;

public abstract class BeanTestCase extends TestCase {

	private static final String TEST_STRING_VAL1 = "Some Value";
	private static final String TEST_STRING_VAL2 = "Some Other Value";

	/**
	 * TODO: currently there is no way to check if there are extra checks in the equal.
	 * TODO: what about testing equals in subclasses?
	 * 
	 * @param classUnderTest
	 */
	public static void _assertMeetsEqualsContract(Class classUnderTest) {
		Object o1;
		Object o2;
		try {
			// Get Instances
			o1 = classUnderTest.newInstance();
			o2 = classUnderTest.newInstance();
			assertTrue(
					"Instances with default constructor not equal (o1.equals(o2))",
					o1.equals(o2));
			assertTrue(
					"Instances with default constructor not equal (o2.equals(o1))",
					o2.equals(o1));

			for (Field field : classUnderTest.getDeclaredFields()) {
	        	if(field.isAnnotationPresent(Equals.class)) {
					// Reset the instances
					o1 = classUnderTest.newInstance();
					o2 = classUnderTest.newInstance();

					field.setAccessible(true);

					// set field it to ValueA

					if (field.getType() == String.class) {
						field.set(o1, TEST_STRING_VAL1);
					} else if (field.getType() == boolean.class) {
						field.setBoolean(o1, true);
					} else if (field.getType().isAssignableFrom(Boolean.class)) {
						field.set(o1, Boolean.TRUE);
					} else if (field.getType() == short.class) {
						field.setShort(o1, (short) 1);
					} else if (field.getType().isAssignableFrom(Short.class)) {
						field.set(o1, Short.valueOf((short)1));
					} else if (field.getType() == long.class) {
						field.setLong(o1, (long) 1);
					} else if (field.getType().isAssignableFrom(Long.class)) {
						field.set(o1, Long.valueOf(1));
					} else if (field.getType() == float.class) {
						field.setFloat(o1, (float) 1);
					} else if (field.getType().isAssignableFrom(Float.class)) {
						field.set(o1, Float.valueOf(1));
					} else if (field.getType() == int.class) {
						field.setInt(o1, 1);
					} else if (field.getType().isAssignableFrom(Integer.class)) {
						field.set(o1, Integer.valueOf(1));
					} else if (field.getType() == byte.class) {
						field.setByte(o1, (byte) 1);
					} else if (field.getType().isAssignableFrom(Byte.class)) {
						field.set(o1, Byte.valueOf((byte)1));
					} else if (field.getType() == char.class) {
						field.setChar(o1, (char) 1);
					} else if (field.getType().isAssignableFrom(Character.class)) {
						field.set(o1, Character.valueOf((char)1));
					} else if (field.getType() == double.class) {
						field.setDouble(o1, (double) 1);
					} else if (field.getType().isAssignableFrom(Double.class)) {
						field.set(o1, Double.valueOf(1));
					} else if (field.getType().isEnum()) {
						field.set(o1, field.getType().getEnumConstants()[0]);
					} else if (Object.class.isAssignableFrom(field.getType())) {
						 System.out.println("got here " + field.getType());
						 field.set(o1, field.getType().newInstance());
						 field.set(o2, null);
					} else {
						fail("Don't know how to set a " + field.getType().getName());
					}

					assertFalse("Instances with o1 having " + field.getName()
							+ " set and o2 having it not set are equal", o1
							.equals(o2));

					field.set(o2, field.get(o1));

					assertTrue(
							"After setting o2 with the value of the object in o1, the two objects in the field are not equal",
							field.get(o1).equals(field.get(o2)));

					assertTrue(
							"Instances with o1 having "
									+ field.getName()
									+ " set and o2 having it set to the same object of type "
									+ field.get(o2).getClass().getName()
									+ " are not equal", o1.equals(o2));

					// set field it to ValueB

					if (field.getType() == String.class) {
						field.set(o2, TEST_STRING_VAL2);
					} else if (field.getType() == boolean.class) {
						field.setBoolean(o2, false);
					} else if (field.getType().isAssignableFrom(Boolean.class)) {
						field.set(o2, Boolean.FALSE);
					} else if (field.getType() == short.class) {
						field.setShort(o2, (short) 0);
					} else if (field.getType().isAssignableFrom(Short.class)) {
						field.set(o2, Short.valueOf((short)0));
					} else if (field.getType() == long.class) {
						field.setLong(o2, (long) 0);
					} else if (field.getType().isAssignableFrom(Long.class)) {
						field.set(o2, Long.valueOf(0));
					} else if (field.getType() == float.class) {
						field.setFloat(o2, (float) 0);
					} else if (field.getType().isAssignableFrom(Float.class)) {
						field.set(o2, Float.valueOf(0));
					} else if (field.getType() == int.class) {
						field.setInt(o2, 0);
					} else if (field.getType().isAssignableFrom(Integer.class)) {
						field.set(o2, Integer.valueOf(0));
					} else if (field.getType() == byte.class) {
						field.setByte(o2, (byte) 0);
					} else if (field.getType().isAssignableFrom(Byte.class)) {
						field.set(o2, Byte.valueOf((byte)0));
					} else if (field.getType() == char.class) {
						field.setChar(o2, (char) 0);
					} else if (field.getType().isAssignableFrom(Character.class)) {
						field.set(o2, Character.valueOf((char)0));
					} else if (field.getType() == double.class) {
						field.setDouble(o2, (double) 0);
					} else if (field.getType().isAssignableFrom(Double.class)) {
						field.set(o2, Double.valueOf(0));
					} else if (field.getType().isEnum()) {
						field.set(o2, field.getType().getEnumConstants()[1]);
					} else if (Object.class.isAssignableFrom(field.getType())) { // if its an object.
						//field.set(o2, field.getType().newInstance());
						field.set(o2, null);
					} else {
						fail("Don't know how to set a " + field.getType().getName());
					}

					// make the final asserts after setting the field to be
					// different.

					assertFalse(
							"After setting o2 with a different object than what is in o1, the two objects in the field are equal. "
									+ "This is after an attempt to walk the fields to make them different",
							field.get(o1).equals(field.get(o2)));
					assertFalse(
							"Instances with o1 having "
									+ field.getName()
									+ " set and o2 having it set to a different object are equal",
							o1.equals(o2));
	        	}
			}

		} catch (InstantiationException e) {
			e.printStackTrace();
			throw new AssertionError(
					"Unable to construct an instance of the class under test");
		} catch (IllegalAccessException e) {
			e.printStackTrace();
			throw new AssertionError(
					"Unable to construct an instance of the class under test");
		}
	}

	
	public static void _assertMeetsHashCodeContract(Class classUnderTest) {
		try {
			for (Field field : classUnderTest.getDeclaredFields()) {
	        	if(field.isAnnotationPresent(Equals.class)) {
					Object o1 = classUnderTest.newInstance();
					int initialHashCode = o1.hashCode();

					field.setAccessible(true);
					if (field.getType() == String.class) {
						field.set(o1, TEST_STRING_VAL1);
					} else if (field.getType() == boolean.class) {
						field.setBoolean(o1, true);
					} else if (field.getType().isAssignableFrom(Boolean.class)) {
						field.set(o1, Boolean.TRUE);
					} else if (field.getType() == short.class) {
						field.setShort(o1, (short) 1);
					} else if (field.getType().isAssignableFrom(Short.class)) {
						field.set(o1, (short)1);
					} else if (field.getType() == long.class) {
						field.setLong(o1, (long) 1);
					} else if (field.getType().isAssignableFrom(Long.class)) {
						field.set(o1, Long.valueOf(1));
					} else if (field.getType() == float.class) {
						field.setFloat(o1, (float) 1);
					} else if (field.getType().isAssignableFrom(Float.class)) {
						field.set(o1, Float.valueOf(1));
					} else if (field.getType() == int.class) {
						field.setInt(o1, 1);
					} else if (field.getType().isAssignableFrom(Integer.class)) {
						field.set(o1, Integer.valueOf(1));
					} else if (field.getType() == byte.class) {
						field.setByte(o1, (byte) 1);
					} else if (field.getType().isAssignableFrom(Byte.class)) {
						field.set(o1, Byte.valueOf((byte)1));
					} else if (field.getType() == char.class) {
						field.setChar(o1, (char) 1);
					} else if (field.getType().isAssignableFrom(Character.class)) {
						field.set(o1, Character.valueOf((char)1));
					} else if (field.getType() == double.class) {
						field.setDouble(o1, (double) 1);
					} else if (field.getType().isAssignableFrom(Double.class)) {
						field.set(o1, Double.valueOf(1));
					} else if (field.getType().isEnum()) {
						field.set(o1, field.getType().getEnumConstants()[0]);
					} else if (Object.class.isAssignableFrom(field.getType())) {
						if(field.get(o1) == null) {
							field.set(o1, field.getType().newInstance());							
						} else {
							field.set(o1, null);
						}
					} else {
						fail("Don't know how to set a " + field.getType().getName());
					}
					int updatedHashCode = o1.hashCode();
					assertFalse(
							"The field "
									+ field.getName()
									+ " was not taken into account for the hashCode contract ",
							initialHashCode == updatedHashCode);
	        		
	        	}
			}
		} catch (InstantiationException e) {
			e.printStackTrace();
			throw new AssertionError(
					"Unable to construct an instance of the class under test");
		} catch (IllegalAccessException e) {
			e.printStackTrace();
			throw new AssertionError(
					"Unable to construct an instance of the class under test");
		}
	}
}

The following test case is designed to test the equals and hashcode of the TestBean class.

src/test/java/com/test/TestBeanTest.java

package com.test;

public class TestBeanTest extends BeanTestCase {

	public void testEqualsContractMet() {
		assertMeetsEqualsContract(TestBean.class);
	}
	public void testHashCodeContractMet() {
		assertMeetsHashCodeContract(TestBean.class);
	}
}

Run the above test case and it should pass.

mvn test

The next class we will extend the TestBean and introduce the open member variable in the sub class. We will write a test case that will check for the proper implementation of the equals and hashcode.

src/main/java/com/test/TestBeanSubClass.java

package com.test;

public class TestBeanSubClass extends TestBean {
	@Equals
	private boolean open;

// getters setters

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = super.hashCode();
		result = prime * result + (open ? 1231 : 1237);
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (!super.equals(obj))
			return false;
		if (getClass() != obj.getClass())
			return false;
		TestBeanSubClass other = (TestBeanSubClass) obj;
		if (open != other.open)
			return false;
		return true;
	}
}

The following is a test case that will check for the proper implementation of the equals and hashcode method.

src/test/java/com/test/TestBeanSubClassTest.java

package com.test;

public class TestBeanSubClassTest extends BeanTestCase {
	public void testEqualsContractMet2() {
		assertMeetsEqualsContract(TestBeanSubClass.class);
	}
	public void testHashCodeContractMet2() {
		assertMeetsHashCodeContract(TestBeanSubClass.class);
	}
}

References

The implementation here differs from the one described in the following link in the fact that reflection is NOT used during runtime.

http://www.platypusinnovation.com/view.html?id=201

Base implementation of the above code was taken from the following URL.

http://blog.coryfoy.com/2008/05/unit-testing-equals-and-hashcode-of-java-beans/

Library to generate various boilerplate code during runtime.

http://projectlombok.org/

Appendix

Dealing with Object Attributes

Object Attributes are expected to implement their own equals method. The test case that checks the parent’s equals method should not be responsible for checking the equals method of object attributes. Instead it will only check to see if the parent bean’s equals() method calls the sub bean’s equals as part of the comparison if the sub bean was marked with the @Equals annotation. The sub bean should be tested separately using its own test case.

We can check to see if equals method is called by exploiting the contract It has with the system.

  • Contract #1: The equals() method returns true when you pass in the same object reference as an argument. Example: this.equals(this) results in true.
  • Contract #2 The equals() method returns false when you pass in a null as the argument. Example: this.equals(null) results in false.

During our test case we will create 2 instances of TestBean class and set instance of SubBean in both instances to be the same reference. We will call the testBeanA.equals(testBeanB) method and it should return true. We will change one instance of TestBean to have a null reference for the subBean attribute and call the testBeanA.equals(testBeanB) method. It should return false since testBeanA.subBean has a reference and testBeanB.subBean is null.

Example: the following “testBeanA.getSubBean().equals(testBeanB.getSubBean())” will return false

04
Apr
10

Using JMock2 to Unit Test Java Code

This page describes the usage of JMock2 framework with JUnit in order to unit test Java code.

Background

No man is an island. By now most everyone has heard this phrase that means that “Human beings do not thrive when isolated from others.” You can say the same for Java classes. Even in the most well designed systems there are always some sort of dependency on other classes. This makes unit testing difficult. This could be the reason developers are turned off from unit testing (not counting laziness).

The JMock2 Framework allows you to create mock implementations. These mock implementations are coded to to behave in a predefined way by the unit tester, then they are injected into Model classes. The model classes are tricked into thinking that they are dealing with actual implementation classes. This allows the developer to setup specific data scenarios that allow the code to be tested.

To conduct effective unit tests you need to make sure that the data does not change. This means you can’t use the database. This is an absolute requirement for 2 reasons.

  1. The data in the database is not static. Databases are shared resource. Someone can always come and yank the data underneath your test case.
  2. The database is slow. Unit tests should be run often and quickly. A slow running test case will make the developer not want to run the test case.

Following MVC Best Practices

An important aspect of writing test friendly and reliable code is to follow MVC best practices. This means that your View logic must be separated from your model and Controller. In addition the model component should be further broken up into parts. One part has the business logic (Model) and the other has the data access logic. Classes that interact with the database, web services, or File systems are to be called DataManagers or DAO’s.

Dont Test your Data Access Layer

In the grand scheme of things the job of DataManagers or DAO’s are pretty simple. These classes convert data back and fourth between the bean representation and the physical representation (network storage, database, or file system). Once you set them up they will continue to perform that task. Your testing effort should not focus on testing your Data Access Layer.. Let you database, or operating system vendor vendor be responsible for this.

Model Layer

The model layer should contain the business logic for your application. It should not make any assumptions on who is invoking calls on it. For example there must be no reference to the Servlet API in this layer. The model layer should communicate to the Data Access Layer thru interfaces. This is the concept of programming by interfaces.

Testing The Model Layer with JMock2

Once you program by interface you can swap out your data managers with Mock implementations and your model will think it is dealing with the real data manager. Doing things this way tricks your Model into thinking the data is coming from the database. This allow you to setup data scenarios to test specific conditions in your model. You can using code coverage analysis tools like eclEMMA in eclipse to ensure you have reached 100% code coverage in your test cases.

Requirements

  • Basic Understanding of JUnit
  • Basic understanding of Maven (see maven tutorial on the right-nav)
  • Understanding of Programming by Interface

Example Code

The following example will show a very basic usage scenario of Jmock2 with Junit.

First step is to create the project using a maven archetype. Open up the command prompt and navigate to an empty directory.

mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart

groupId: com.test
artifactId: jmockTest

Answer the rest of the questions with defaults “Just hit the enter key”

Modify the pom.xml

Modify the file to look like this…

<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/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.test</groupId>
  <artifactId>jmockTest</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>jmockTest</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  <dependency>
    <groupId>org.jmock</groupId>
    <artifactId>jmock</artifactId>
    <version>2.5.1</version>
    <scope>test</scope>
</dependency>
</dependencies>

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.0.2</version>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
        </plugins>
</build>
</project>

Save and exit.

If you are using eclipse then you may want to regenerate your project before importing it into eclipse.
do this by typing:
mvn eclipse:clean eclipse:eclipse

src/main/java/com/test/Account.java

package com.test;

import java.math.BigDecimal;

/**
 * Simple POJO bean that represents an account.
 */
public class Account {
	public String getAccountNumber() {
		return accountNumber;
	}
	public void setAccountNumber(String accountNumber) {
		this.accountNumber = accountNumber;
	}
	public BigDecimal getBalance() {
		return balance;
	}
	public void setBalance(BigDecimal balance) {
		this.balance = balance;
	}
	private String accountNumber;
	private BigDecimal balance;
}

src/main/java/com/test/AccountDataManager.java

package com.test;

public interface AccountDataManager {
	public void saveAccount(Account accountA);
	public Account getAccountById(String accountAId);
}

src/main/java/com/test/AccountDataManagerImpl.java

package com.test;

public class AccountDataManagerImpl implements AccountDataManager {
	/**
	 * This code is simple no need to test Vendor JDBC implementation...
	 */
	public void saveAccount(Account accountA) {
		// some JDBC code to save account to database
		// goes here.
	}

	/**
	 * This code is simple no need to test Vendor JDBC implementation...
	 */
	public Account getAccountById(String accountAId) {
		// some JDBC code to get account from database
		// goes here.
		return null;
	}
}

src/main/java/com/test/AccountTransferModel.java

package com.test;

import java.math.BigDecimal;

public interface AccountTransferModel {

	/**
	 * Transfer money from account a to account b. This should be executed from
	 * within a transaction. (See spring framework)
	 *
	 * @param a
	 * @param b
	 */
	public abstract void transferMoney(String accountAId, String accountBId,
			BigDecimal amount);

}

src/main/java/com/test/AccountTransferModelImpl.java

package com.test;

import java.math.BigDecimal;

/**
 * This class is where the business logic is implemented.
 */
public class AccountTransferModelImpl implements AccountTransferModel {
	/**
	 * Since we are using an interface here we will use JMock2 to
	 * implement the below interface with a fake stub class.
	 */
	private AccountDataManager accountDataManager;

	public void setAccountDataManager(AccountDataManager accountDataManager) {
		this.accountDataManager = accountDataManager;
	}

	/**
	 * This is the method we are testing...
	 */
	public void transferMoney(String accountAId, String accountBId, BigDecimal amount) {
		Account accountA = accountDataManager.getAccountById(accountAId);
		Account accountB = accountDataManager.getAccountById(accountBId);

		if(accountA==null) {
			throw new IllegalArgumentException("account does not exist.");
		}
		if(accountB==null) {
			throw new IllegalArgumentException("account does not exist.");
		}

		if(accountA.getBalance().compareTo(amount) < 0 ) {
			throw new IllegalArgumentException("account A does not have enough balance.");
		}
		accountA.setBalance(accountA.getBalance().subtract(amount));
		accountB.setBalance(accountB.getBalance().add(amount));

		accountDataManager.saveAccount(accountA);
		accountDataManager.saveAccount(accountB);

	}
}

Finally we write our test case.

src/test/java/com/test/AppTest.java

package com.test;

import java.math.BigDecimal;

import junit.framework.TestCase;

import org.jmock.Expectations;
import org.jmock.Mockery;

/**
 * Test to check Money Transfers Between Accounts
 */
public class AppTest extends TestCase {

	public void testTransfersMoneyBetweenAccounts() {
		Mockery context = new Mockery();
		final AccountDataManager accountDataManager = context.mock(AccountDataManager.class);
		AccountTransferModelImpl accountTransferModel = new AccountTransferModelImpl();
		accountTransferModel.setAccountDataManager(accountDataManager);

		// setup the data
		final Account accountA = new Account();
		accountA.setAccountNumber("abc123");
		accountA.setBalance(BigDecimal.valueOf(100));

		final Account accountB = new Account();
		accountB.setAccountNumber("xyz123");
		accountB.setBalance(BigDecimal.valueOf(100));

		// set the expectations
		// if this next section looks odd then go visit the jMock2 site
		// for documentation on how to specify expectations
	    context.checking(new Expectations() {{
	        allowing (accountDataManager).getAccountById("abc123");
	        will(returnValue(accountA));
	        allowing (accountDataManager).getAccountById("xyz123");
	        will(returnValue(accountB));

	        oneOf(accountDataManager).saveAccount(accountA);
	        oneOf(accountDataManager).saveAccount(accountB);
	    }});
		// execute the code

	    accountTransferModel.transferMoney("abc123", "xyz123", new BigDecimal(100));

	    // make the assertions
	    assertEquals(accountA.getBalance(),BigDecimal.valueOf(0));
	    assertEquals(accountB.getBalance(),BigDecimal.valueOf(200));
	}
}

Run the code

Run the code by executing it thru eclipse.

Or type mvn test on the command line.

System should return success condition

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.test.AppTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.061 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

Analyse the code

Just Set a couple of breakpoints and trace thru the code using your favorite debugging tool.

References

Thats all for now.




Enter your email address to subscribe to this blog and receive notifications of new posts by email.

Join 77 other followers

September 2017
S M T W T F S
« Mar    
 12
3456789
10111213141516
17181920212223
24252627282930

Blog Stats

  • 830,829 hits