Step-by-step tutorial

Writing good concise tests for application components can be quite cumbersome and difficult. Especially, when they have a lot of dependencies to other beans and you do not want to manually write mock objects to fulfil all of them. On the other hand, you do not want to run too many slow integration tests.



With Needle you can accomplish those goals in very comfortable way.



This turorial demonstrates how to test Java EE based applications with Needle and shows you how easy it is to write unit tests with Needle. For this purpose we will implement a simple Java EE 6 Blog application with JPA, EJB, CDI and JSF step-by-step and test the components.

1. Project setup

Let’s start by generating a maven project skeleton with a maven archetype.

mvn archetype:generate \
-DarchetypeArtifactId=jboss-javaee6-webapp-ear-archetype-blank \
-DarchetypeGroupId=org.jboss.spec.archetypes \
-DarchetypeVersion=7.0.2.CR2

The generated application is a multi-module maven project, packaged as an ear archive as presented below. It includes nothing else as the complete maven configuration for a modular Java EE 6 application. To build and package the application use the maven command mvn clean package.

Illustration 1: Initial project structure

2. JPA Module

To preserve the modularity of the application, we create an own maven module for the domain model and move the existing persistence.xml from the EJB module to the src/main/resources/META-INF directory of the new JPA module.

We want to save our blog entry with the Java Persistence API. A blog entry has a title, creation date and the content. Only authenticated users are allowed to post some entries and comment a blog post.

The following illustration shows the JPA Entities and the relationships between them.

Illustration 2: Domain model

For each entity we need a primary key. The implementation is done in an abstract superclass that is annotated with @MappedSuperclass. The id field is annotated with @GeneratedValue, which means the JPA provider will generate the primary key for us.

The User Entity is pretty simple. The Entity inherits from our AbstractEntity class, which provides the id and the version field.

Instead of implementing an anemic domain model the related business logic and the relationships between them are implemented in our entities.

To ensure consistent data, the mandatory field are validated using annotations of the Bean Validation API.

@Entity
    public class User extends AbstractEntity {

    private static final long serialVersionUID = 1L;

    @NotEmpty
    @Column(nullable = false)
    private String firstname;

    @NotEmpty
    @Column(nullable = false)
    private String surname;

    @NotEmpty
    @Column(nullable = false, unique = true)
    private String username;

    @NotEmpty
    @Column(nullable = false)
    private String password;

    // some getter and setter

    public void setPassword(String password) {
        this.password = new BasicPasswordEncryptor()
                .encryptPassword(password);
    }

    public boolean verifyPassword(String password) {
        return new BasicPasswordEncryptor().checkPassword(password,
        this.password);
    }
}

Now we write a test case for our domain model, but first we need to setup the test infrastructure.

Therefore we have to add the test dependencies. The dependencies are declared in the parent project object model (pom) of our maven project. The declared dependencies are usable in all the modules of our application. The visibility is restricted to the test scope.

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>3.1</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>de.akquinet.jbosscc</groupId>
<artifactId>jbosscc-needle</artifactId>
<version>2.1</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.6.4</version>
<scope>test</scope>
</dependency>

<dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.0.1.Final</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.2.8</version>
<scope>test</scope>
</dependency>
</dependencies>

Our tests are based on JUnit 4 and the EasyMock framework. EasyMock is a framework to create Mock Objects to test components in isolation. The database we are using is the fast in memory HSQL Database. The HSQL Database is a 100% pure Java database, which can be setup to run completely in memory.

For unit testing purposes we will implement a reusable test data builder. With the test data builder pattern, the implementation for creating a test object provides methods that can be used to configure the test object. Properties that are not configured use default values. The builder methods can be chained together and provide transient or persistent test data for the test cases.

The following implementation shows the UserTestdataBuilder, which is responsible to provide valid test data for a User instance. The test data builder inherits from an abstract implementation of the Needle framework. The base class provides the buildAndSave() method to create and persist entities.

public class UserTestdataBuilder extends AbstractTestdataBuilder
    <User> {

    private static final String DEFAULT_PASSWORD = "secret";
    private String withUsername;
    private String withFirstname;
    private String withSurname;

    public UserTestdataBuilder() {
        super();
    }

    public UserTestdataBuilder(EntityManager entityManager) {
        super(entityManager);
    }

    public UserTestdataBuilder withUsername(String username) {
        this.withUsername = username;
        return this;
    }

    public UserTestdataBuilder withFirstname(String firstname) {
        this.withFirstname = firstname;
        return this;
    }

    public UserTestdataBuilder withSurname(String surname) {
        this.withSurname = surname;
        return this;
    }

    private String getFirstname() {
        return withFirstname != null ? withFirstname : "Max";
    }

    private String getSurname() {
        return withSurname != null ? withSurname : "Muster";
    }

    private String getUsername() {
        return withUsername != null ? withUsername :
        "mmuster" + getId();
    }

    @Override
    public User build() {
        final User user = new User();
        user.setUsername(getUsername());
        user.setFirstname(getFirstname());
        user.setSurname(getSurname());

        user.setPassword(DEFAULT_PASSWORD);

        return user;
    }
}

To setup the JPA Layer in the test environment, we need to define the following persistence unit in the persistence.xml file under the src/test/resources/META-INF directory. This configuration file is needed to bootstrap the JPA layer. The configuration contains all the information to connect to the database and the information for the schema generation. The schema DDL will be exported to the database when the SessionFactory is created and dropped when the SessionFactory is closed explicitly.

<persistence version="2.0"
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">

<persistence-unit name="TestDataModel" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>

<class>de.akquinet.jbosscc.blog.User</class>
<class>de.akquinet.jbosscc.blog.Comment</class>
<class>de.akquinet.jbosscc.blog.BlogEntry</class>

<properties>

<property name="javax.persistence.jdbc.user" value="sa" />
<property name="javax.persistence.jdbc.password" value="" />
<property name="javax.persistence.jdbc.url" value="jdbc:hsqldb:." />
<property name="javax.persistence.jdbc.driver"
value="org.hsqldb.jdbcDriver" />
<property name="hibernate.dialect"
value="org.hibernate.dialect.HSQLDialect" />

<property name="hibernate.hbm2ddl.auto" value="create-drop" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="false" />
</properties>

</persistence-unit>
</persistence>

A common issue in unit tests that access a real database is their effect on the state of the persistence store. They usually expect a certain state at the beginning and alter it. To address that problem, optional Database operations can be executed before and after test execution, in order to clean up or set up data.

We using the default implementation of the HSQLDeleteOperation and activate it with the following configuration in the needle.properties File under the src/test/resources directory.

db.operation=
        de.akquinet.jbosscc.needle.db.operation.hsql.HSQLDeleteOperation

This database operation will delete all the content of the database after test execution.

Now we completed the configuration of our unit tests and we can write our fist test.

In the following UserTest, Needle will automatically create and provide the EntityManager instance for the test case. Therefore Needle provides JUnit Rules to extend JUnit. Rules are basically wrappers around test methods. They can execute code before, after or instead of a test method.

public class UserTest {

    @Rule
    public DatabaseRule databaseRule = new DatabaseRule();

    private EntityManager entityManager =
            databaseRule.getEntityManager();

    @Test
    public void testPersist() throws Exception {
        User user = new UserTestdataBuilder(entityManager)
                .buildAndSave();

        User userFromDb = entityManager.find(User.class, user.getId());

        Assert.assertEquals(user.getId(), userFromDb.getId());
        Assert.assertNotSame(user, userFromDb);
    }

    @Test(expected = PersistenceException.class)
    public void testUniqueUsername() throws Exception {
        new UserTestdataBuilder(entityManager).withUsername("username")
                .buildAndSave();

        databaseRule.getTransactionHelper().executeInTransaction(
                new VoidRunnable() {

                    @Override
                    public void doRun(EntityManager entityManager)
                            throws Exception {
                        entityManager.persist(
                                new UserTestdataBuilder()
                                        .withUsername("username")
                                        .build());
                    }
                });

    }

    @Test
    public void testVerifyPassword() throws Exception {
        User user = new UserTestdataBuilder().build();
        Assert.assertTrue(user.verifyPassword("secret"));
        Assert.assertFalse(user.verifyPassword("other"));
    }
}

The first test case is very simple. With our UserTestdataBuilder we create and persist a User instance and retrieve the persisted User from the underling database. If the test passes, we know the mapping of our JPA entity is correct.

A user of the Blog Application needs a unique username. This will be tested in the second test case. First we create and persist a user and then we try to persist a second user in an own transaction with the same username and expect a PersistenceException.

In the third test case, we test the verification of the password. Therefore we create only a transient User instance and test the logic with a wrong and correct password.

Let’s go on with the implementation of the domain model. We need a BlogEntry entity. The entity has a title, creation date and a property for the blog content. Only registered users are allowed to write blog entries and post them. Therefore we implemented a one-to-one relationship between the User and the BlogEntry.

@Entity
public class BlogEntry extends AbstractEntity {

    private static final long serialVersionUID = 1L;

    @NotEmpty
    @Column(nullable = false)
    private String title;

    @Lob
    @NotEmpty
    private String content;

    @NotNull
    @ManyToOne(optional = false)
    private User author;

    @NotNull
    @Temporal(TemporalType.TIMESTAMP)
    private Date created = new Date();

    // some getter and setter
}

Also for the BlogEntry entity, we implement a test data builder. To satisfy the User relationship, the UserTestdataBuilder is used to provide a valid user instance as author of the blog entry.

public class BlogEntryTestdataBuilder extends
        AbstractTestdataBuilder<BlogEntry> {

    private static final String CONTENT = "Lorem ...";
    private static final String TITLE = "Lorem ...";

    private User withAuthor;
    private String withContent;
    private String withTitle;

    public BlogEntryTestdataBuilder() {
        super();
    }

    public BlogEntryTestdataBuilder(EntityManager entityManager) {
        super(entityManager);
    }

    public BlogEntryTestdataBuilder withAuthor(User author) {
        withAuthor = author;
        return this;
    }

    public BlogEntryTestdataBuilder withContent(String content) {
        withContent = content;
        return this;
    }

    public BlogEntryTestdataBuilder withTitle(String title) {
        withTitle = title;
        return this;
    }

    private User getAuthor() {
        if (withAuthor != null) {
            return withAuthor;
        }

        return hasEntityManager() ?
            new UserTestdataBuilder(getEntityManager()).buildAndSave() :
            new UserTestdataBuilder().build();
    }

    private String getTitle() {
        return withTitle != null ? withTitle : TITLE;
    }

    private String getContent() {
        return withContent != null ? withContent : CONTENT;
    }

    @Override
    public BlogEntry build() {
        BlogEntry blogEntry = new BlogEntry();
        blogEntry.setAuthor(getAuthor());
        blogEntry.setTitle(getTitle());
        blogEntry.setContent(getContent());

        return blogEntry;
    }

}

The BlogEntry implementation can easily tested as well. The first test case will verify the JPA mapping of the BlogEntry by persisting an instance created with the BlogEntryTestdataBuilder. The second test case checks the not null constraint of the author property by expecting a ConstraintViolationException.


public class BlogEntryTest {

    @Rule
    public DatabaseRule databaseRule = new DatabaseRule();

    private EntityManager entityManager = databaseRule
            .getEntityManager();

    @Test
    public void testPersist() throws Exception {
        BlogEntry blogEntry = new BlogEntryTestdataBuilder(entityManager)
                .buildAndSave();

        BlogEntry blogEntryFromDb = entityManager.find(BlogEntry.class,
                blogEntry.getId());

        Assert.assertEquals(blogEntry.getId(), blogEntryFromDb.getId());
        Assert.assertNotSame(blogEntry, blogEntryFromDb);
    }

    @Test(expected = ConstraintViolationException.class)
    public void testPersistWithoutAuthor() throws Exception {
        BlogEntry blogEntry = new BlogEntryTestdataBuilder(entityManager)
                .build();
        blogEntry.setAuthor(null);

        databaseRule.getTransactionHelper().executeInTransaction(
                new VoidRunnable() {

                    @Override
                    public void doRun(EntityManager entityManager)
                            throws Exception {
                        entityManager.persist(blogEntry);

                    }
                });
    }

}

The third JPA entity represent the comments of a blog post. A comment has also a reference to an author and an releationship to the BlogEntry entity.

@Entity
public class Comment extends AbstractEntity {

    private static final long serialVersionUID = 1L;

    @NotNull
    @ManyToOne(optional = false)
    private User author;

    @NotNull
    @ManyToOne(optional = false)
    private BlogEntry blogEntry;

    @Lob
    @NotEmpty
    private String content;

    @NotNull
    @Temporal(TemporalType.TIMESTAMP)
    private Date created = new Date();
    // some getter and setter

}

The BlogEntry can now be extended with a one-to-many relationship to the comment entities. This association is a bi-directional relationship between the Comment entities.

@Entity
public class BlogEntry extends AbstractEntity {
    // ...

    @OneToMany(mappedBy = "blogEntry", cascade = {CascadeType.REMOVE,
            CascadeType.REFRESH})
    private List
            <Comment> comments = new ArrayList
            <Comment>();

    // some getter and setter

    public List
    <Comment> getComments() {
        return Collections.unmodifiableList(comments);
    }

    public void addComment(Comment comment) {
        comments.add(comment);
    }

    public boolean removeComment(Comment comment) {
        return comments.remove(comment);
    }

    // ...

}

For the Comment entity we will also implemet a test data builder. The following code shows the implementation. The test data builder uses the UserTestdataBuilder and the BlogEntryTestdataBuilder to provide valid instances of an User and a BlogEntry to fullfil the contraints.


public class CommentTestdataBuilder extends
        AbstractTestdataBuilder<Comment> {

    private static final String CONTENT = "Lorem ipsum dolor sit amet";

    private User withAuthor;
    private BlogEntry withBlogEntry;
    private String withContent;

    public CommentTestdataBuilder() {
        super();
    }

    public CommentTestdataBuilder(EntityManager entityManager) {
        super(entityManager);
    }

    public CommentTestdataBuilder withAuthor(User author) {
        withAuthor = author;
        return this;
    }

    public CommentTestdataBuilder withContent(String content) {
        withContent = content;
        return this;
    }

    public CommentTestdataBuilder withBlogEntry(BlogEntry blogEntry) {
        withBlogEntry = blogEntry;
        return this;
    }

    private User getAuthor() {
        if (withAuthor != null) {
            return withAuthor;
        }

        return hasEntityManager() ? new UserTestdataBuilder(
                getEntityManager()).buildAndSave() :
                new UserTestdataBuilder().build();
    }

    private BlogEntry getBlogEntry() {
        if (withBlogEntry != null) {
            return withBlogEntry;
        }

        return hasEntityManager() ?
                new BlogEntryTestdataBuilder(getEntityManager())
                        .buildAndSave() :
                new BlogEntryTestdataBuilder().build();
    }

    private String getContent() {
        return withContent != null ? withContent : CONTENT;
    }

    @Override
    public Comment build() {
        Comment comment = new Comment();
        comment.setAuthor(getAuthor());
        comment.setBlogEntry(getBlogEntry());
        comment.setContent(getContent());
        return comment;
    }

}

The CommentTest creates a Comment entity with an author reference and a reference to a BlogEntry entity. The User and BlogEntry instance will be created and persisted by the test data builder.

public class CommentTest {

    @Rule
    public DatabaseRule databaseRule = new DatabaseRule();

    private EntityManager entityManager = databaseRule
            .getEntityManager();

    @Test
    public void testPersist() throws Exception {
        Comment comment = new CommentTestdataBuilder(entityManager)
                .buildAndSave();

        Comment commentFromDb = entityManager.find(Comment.class,
                comment.getId());

        Assert.assertEquals(comment.getId(), commentFromDb.getId());
        Assert.assertNotSame(comment, commentFromDb);
    }
}

3. EJB Module

For the business logic we use Enterprise Java Bean (EJB) components. This tier includes the data access logic for the domain model. Each data access object inherits from a generic base class. The base class provides common operations such as loading and saving entities.

We are using a stateless session bean for the UserDaoBean implementation. For authentication, we will implement a method to find a user by the username.

@Stateless
public class UserDaoBean extends AbstractDaoBean
        <User> implements UserDao {

    @Inject
    private Logger log;

    @Override
    public User findByUsername(final String username) {

        log.info("find user with username " + username);

        CriteriaBuilder builder = getCriteriaBuilder();
        CriteriaQuery
            <User> query = builder.createQuery(User.class);

        Root
            <User> user = query.from(User.class);

        query.where(builder.equal(user.get(User_.username), username));

        return getSingleResult(query);
    }
}

In all unit tests we want to reuse the test data builder implementation of the JPA Module. Therefore we package those compiled tests in a JAR for general reuse. To do this, we have to configure the maven-jar-plugin as follows:


<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>

To use the attached test jar, we must configure it as dependency with the specific type test-jar:


<dependency>
<groupId>de.akquinet.jbosscc</groupId>
<artifactId>jbosscc-blog-example-jpa</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>

The UserDaoTest tests the query of the UserDaoBean implementation against the in-memory HSQL Database. The database rule provides access to the database via JPA and executes the configured database operation. The Needle rule does the real magic: it scans the test for all fields annotated with @ObjectUnderTest and initializes these tested components by injection of their dependencies.

public class UserDaoTest {

    @Rule
    public DatabaseRule databaseRule = new DatabaseRule();

    @Rule
    public NeedleRule needleRule = new NeedleRule(databaseRule);

    @Inject
    private EntityManager entityManager;

    @ObjectUnderTest
    private UserDaoBean userDao;

    @Test
    public void testFindByUsername() throws Exception {

        User user = new UserTestdataBuilder(entityManager)
                .buildAndSave();

        User findByUsername = userDao.findByUsername(user.getUsername());

        Assert.assertEquals(user.getId(), findByUsername.getId());

        User other = userDao.findByUsername("name");
        Assert.assertNull(other);
    }

}

We need a second data access object implementation for the BlogEntry entity. The BlogEntryDaoBean has two methods. One method to find blog entries with two parameters to limit the result set. And a other method to count all blog entries for pagination.

@Stateless
public class BlogEntryDaoBean extends AbstractDaoBean
        <BlogEntry> implements BlogEntryDao {

    @Inject
    private Logger log;

    @Override
    public List
            <BlogEntry> find(int maxresults, int firstresult) {
        log.info("find blog entry, max results " + maxresults +
                " next result " + firstresult);

        CriteriaBuilder builder = getCriteriaBuilder();
        CriteriaQuery<BlogEntry> query = builder
                .createQuery(BlogEntry.class);

        Root<BlogEntry> from = query.from(BlogEntry.class);

        query.select(from).orderBy(builder.desc(
                from.get(BlogEntry_.created)));

        return getResultList(query, maxresults, firstresult);
    }

    @Override
    public Long count() {
        CriteriaBuilder builder = getCriteriaBuilder();
        CriteriaQuery<Long> query = builder.createQuery(Long.class);
        Root<BlogEntry> from = query.from(BlogEntry.class);

        query.select(builder.count(from));

        return this.<Long>getTypedSingleResult(query);
    }
}

The BlogEntryDaoTest is pretty similar to the other data access object test implementations. In every test case, we provide the necessary test data for our queries and check the expected results in the assertions.

public class BlogEntryDaoTest {

    @Rule
    public DatabaseRule databaseRule = new DatabaseRule();

    @Rule
    public NeedleRule needleRule = new NeedleRule(databaseRule);

    @Inject
    private EntityManager entityManager;

    @ObjectUnderTest
    private BlogEntryDaoBean blogEntryDao;

    @Test
    public void testFind() throws Exception {
        new BlogEntryTestdataBuilder(entityManager).buildAndSave();
        new BlogEntryTestdataBuilder(entityManager).buildAndSave();
        BlogEntry latest = new BlogEntryTestdataBuilder(entityManager)
                .buildAndSave();

        List<BlogEntry> list = blogEntryDao.find(2, 0);

        Assert.assertEquals(2, list.size());
        Assert.assertEquals(latest.getId(), list.get(0).getId());
    }

    @Test
    public void testCount() throws Exception {
        Long count = blogEntryDao.count();
        Assert.assertEquals(new Long(0), count);

        new BlogEntryTestdataBuilder(entityManager).buildAndSave();

        count = blogEntryDao.count();
        Assert.assertEquals(new Long(1), count);
    }
}

For the Comment entity we need a data access object to query all comments for a specific blog entry.

@Stateless
public class CommentDaoBean extends AbstractDaoBean<Comment>
        implements CommentDao {

    @Inject
    private Logger log;

    @Override
    public List<Comment> findComments(final BlogEntry blogEntry) {
        log.info("find comment for blog entry " + blogEntry);

        CriteriaBuilder builder = getCriteriaBuilder();
        CriteriaQuery<Comment> query = builder
                .createQuery(Comment.class);

        Root<Comment> from = query.from(Comment.class);
        query.select(from)
                .where(builder.equal(from.get(Comment_.blogEntry),
                        blogEntry))
                .orderBy(builder.asc(from.get(Comment_.created)));
        return getResultList(query);
    }
}

In the CommentDaoTest implementation we create a BlogEntry and add some comments to the BlogEntry instance with the test data builder. The assertion checks the expected result of the findComments() method.

public class CommentDaoTest {

    @Rule
    public DatabaseRule databaseRule = new DatabaseRule();

    @Rule
    public NeedleRule needleRule = new NeedleRule(databaseRule);

    @Inject
    private EntityManager entityManager;

    @ObjectUnderTest(implementation = CommentDaoBean.class)
    private CommentDao commentDao;

    @Test
    public void testFind() throws Exception {
        BlogEntry blogEntry1 = new BlogEntryTestdataBuilder(
                entityManager).buildAndSave();
        Comment comment = new CommentTestdataBuilder(entityManager)
                .withBlogEntry(blogEntry1).buildAndSave();

        List<Comment> comments = commentDao.findComments(blogEntry1);
        Assert.assertEquals(1, comments.size());
        Assert.assertEquals(comment.getId(), comments.get(0).getId());

        BlogEntry blogEntry2 = new BlogEntryTestdataBuilder(
                entityManager).buildAndSave();
        comments = commentDao.findComments(blogEntry2);

        Assert.assertEquals(0, comments.size());

    }
}

4. Web / CDI Module

The Web Module contains all the Web logic related components. In this tier we will use Contexts and Dependency Injection (CDI). This is the most complex layer of the application, because in this layer all components are interact with each other and some components are stateful.

As mentioned at the beginning of the post, only authenticated user are allowed to post blog entries and write comments. For this we need a component to verify the username and password. If the user is authorized, the respective user is stored in the session within the Identity instance.

@Named
@RequestScoped
public class Authenticator implements Serializable {

    private static final long serialVersionUID = 1L;

    @Inject
    private Logger log;

    @Inject
    private UserDao userDao;

    @Inject
    private HttpSession session;

    @Inject
    private Identity identity;

    private String username;
    private String password;

    public boolean login() {
        User user = userDao.findByUsername(username);

        if (user != null && user.verifyPassword(password)) {
            identity.setLoggedIn(true);
            identity.setUser(user);
            return true;
        }

        return false;
    }

    public boolean logout() {
        log.info("logout current user");
        session.invalidate();

        return true;

    }

    // username and password getter and setter
}

To test the Authenticator component the database access can be mocked. That means the data access object will be replaced with mock objects. The provided mock objects by the Needle rule can easily injected into the test case for trainings. The Identity dependency of the Authenticator will be provided through the Identity instance of the test case.

public class AuthenticatorTest {

    @Rule
    public NeedleRule needleRule = new NeedleRule();

    @ObjectUnderTest
    private Authenticator authenticator;

    @Inject
    private EasyMockProvider mockProvider;

    @Inject
    private UserDao userDaoMock;

    @Inject
    private HttpSession httpSessionMock;

    @InjectIntoMany
    private Identity identity = new Identity();

    @Test
    public void testLoginFailed() throws Exception {
        EasyMock.expect(
                userDaoMock.findByUsername(EasyMock.
                        <String> anyObject())).andReturn(null);

        mockProvider.replayAll();
        boolean login = authenticator.login();
        Assert.assertFalse(identity.isLoggedIn());
        Assert.assertFalse(login);

        mockProvider.verifyAll();
    }

    @Test
    public void testLoginWithWrongPassword() throws Exception {
        String username = "username";

        EasyMock.expect(
            userDaoMock.findByUsername(username)).andReturn(
            new UserTestdataBuilder().withUsername(username).build());

        mockProvider.replayAll();
        authenticator.setUsername(username);
        authenticator.setPassword("any");
        boolean login = authenticator.login();
        Assert.assertFalse(login);
        Assert.assertFalse(identity.isLoggedIn());
        mockProvider.verifyAll();
    }

    @Test
    public void testLoginSuccess() throws Exception {
        String username = "username";
        User user = new UserTestdataBuilder()
                .withUsername(username).build();
        EasyMock.expect(userDaoMock.findByUsername(username))
                .andReturn(user);

        mockProvider.replayAll();
        authenticator.setUsername(username);
        authenticator.setPassword("secret");
        boolean login = authenticator.login();
        Assert.assertTrue(login);
        Assert.assertTrue(identity.isLoggedIn());
        Assert.assertEquals(identity.getUser(), user);
        mockProvider.verifyAll();
    }

    @Test
    public void testLogout() throws Exception {
        mockProvider.resetToStrict(httpSessionMock);
        httpSessionMock.invalidate();

        mockProvider.replayAll();

        Assert.assertTrue(authenticator.logout());
        mockProvider.verifyAll();
    }
}

The next implementation is a list service for the blog entries. The component contains method to retrieve the blog entries and some methods for the pagination in the web application.

@Named
@RequestScoped
public class BlogEntryListService {

    private static final int MAX_RESULTS = 5;

    @Inject
    private Logger log;

    @Inject
    private BlogEntryDao blogEntryDao;

    private List
            <BlogEntry> resultList;

    private int firstResult = 0;

    public List<BlogEntry> getResultList() {
        log.info("load blog entries");
        if (resultList == null) {
            resultList = blogEntryDao.find(MAX_RESULTS, firstResult);
        }
        return resultList;
    }

    public int getNextFirstResult() {
        return firstResult + MAX_RESULTS;
    }

    public int getPreviousFirstResult() {
        return MAX_RESULTS >= firstResult ? 0 :
                firstResult - MAX_RESULTS;
    }

    public Integer getFirstResult() {
        return firstResult;
    }

    public void setFirstResult(Integer firstResult) {
        log.info("set first result " + firstResult);
        this.firstResult = firstResult;
        this.resultList = null;
    }

    public boolean isPreviousExists() {
        return firstResult > 0;
    }

    public boolean isNextExists() {
        return blogEntryDao.count() > MAX_RESULTS + firstResult;
    }

}

To test the BlogEntryListService component, we can also replace the data access objects with mock objects and train the mock objects by calling the respective methods and return the expected values.

public class BlogEntryListServiceTest {

    @Rule
    public NeedleRule needleRule = new NeedleRule();

    @ObjectUnderTest
    private BlogEntryListService blogEntryListService;

    @Inject
    private EasyMockProvider mockProvider;

    @Inject
    private BlogEntryDao blogEntryDaoMock;

    @Test
    public void testGetResultList() throws Exception {
        EasyMock.expect(blogEntryDaoMock.find(5, 0)).andReturn(
                Arrays.asList(new BlogEntryTestdataBuilder().build()));

        mockProvider.replayAll();
        List<BlogEntry> resultList = blogEntryListService
                .getResultList();
        Assert.assertEquals(1, resultList.size());
        mockProvider.verifyAll();
    }

    @Test
    public void testPagination() throws Exception {

        EasyMock.expect(blogEntryDaoMock.count()).andReturn(9L)
                .anyTimes();

        mockProvider.replayAll();

        Assert.assertEquals(Integer.valueOf(0),
                blogEntryListService.getFirstResult());

        Assert.assertEquals(5, blogEntryListService
                .getNextFirstResult());

        Assert.assertEquals(0, blogEntryListService
                .getPreviousFirstResult());

        Assert.assertEquals(true, blogEntryListService.isNextExists());
        Assert.assertEquals(false, blogEntryListService
                .isPreviousExists());

        blogEntryListService.setFirstResult(
                blogEntryListService.getNextFirstResult());

        Assert.assertEquals(Integer.valueOf(5),
                blogEntryListService.getFirstResult());

        Assert.assertEquals(10, blogEntryListService
                .getNextFirstResult());

        Assert.assertEquals(0, blogEntryListService
                .getPreviousFirstResult());

        Assert.assertEquals(false, blogEntryListService.isNextExists());
        Assert.assertEquals(true, blogEntryListService
                .isPreviousExists());

        mockProvider.verifyAll();
    }
}

Another component is the BlogEntryService. This component manages a BlogEntry instance.

To create a new blog entry we need to know the author. The author is the user that is currently logged in. This user is provided by the Identity instance, which produces the user with the CDI qualifier @CurrentUser.

@Named
@ConversationScoped
public class BlogEntryService implements Serializable {

    private static final long serialVersionUID = 1L;

    @Inject
    private Logger log;

    @Inject
    private BlogEntryDao blogEntryDao;

    @Inject
    private Conversation conversation;

    @Inject
    @CurrentUser
    private User user;

    private Long id;

    private BlogEntry instance;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        begin();

        log.info("set blogentry id " + id);
        this.id = id;
    }

    @Produces
    public BlogEntry getInstance() {
        if (instance == null || (id != null && id != instance.getId())) {
            instance = blogEntryDao.find(id);
        }
        log.info("return blog entry " + instance);
        return instance;
    }

    public boolean newInstance() {
        begin();
        BlogEntry blogEntry = new BlogEntry();
        blogEntry.setAuthor(user);
        instance = blogEntry;
        id = null;
        return true;
    }

    private void begin() {
        if (conversation.isTransient()) {
            conversation.begin();
            log.info("begin conversation with id " + conversation
                    .getId());
        }
    }

    private void end() {
        if (!conversation.isTransient()) {
            conversation.end();
        }
    }

    public boolean persistOrUpdate() {
        if (instance.getId() == null) {
            blogEntryDao.persist(instance);
        } else {
            instance = blogEntryDao.merge(instance);
        }
        id = instance.getId();
        return true;
    }

    public boolean delete() {
        log.info("delete blog entry " + instance);
        blogEntryDao.remove(instance);

        end();
        return true;
    }
}

The Needle framework allows us to implement our own injection provider. This is very useful to provide a specific User instance for our CDI Qualifier @CurrentUser. The injection provider can be used as constructor argument for the Needle Rule.

public class CurrentUserInjectionProvider implements
        InjectionProvider<User> {

    private User currentUser = new UserTestdataBuilder()
            .withUsername("jfrancis").withFirstname("Jerry")
            .withSurname("Francis").build();

    @Override
    public User getInjectedObject(Class<?>injectionPointType) {
        return currentUser;
    }

    @Override
    public boolean verify(InjectionTargetInformation information) {
        return information.isAnnotationPresent(CurrentUser.class);
    }

    @Override
    public Object getKey(InjectionTargetInformation information) {
        return CurrentUser.class;
    }
}

The BlogEntryServiceTest is most complexes test, because we use more then one components that are annotated with @ObjecUnderTest. The components are wired together by using the @InjectIntoMany annotation.

The custom injection provider will parsed as argument to the Needle rule to inject the current user instance into the BlogEntryService.

This test setup allows us to test complex use cases and writing meaningful assertion. For example, in the first test case we will create a new blog entry, set the title and the content and persist the new instance.

public class BlogEntryServiceTest {

    @Rule
    public DatabaseRule databaseRule = new DatabaseRule();

    @Rule
    public NeedleRule needleRule = new NeedleRule(databaseRule,
            new CurrentUserInjectionProvider());

    @ObjectUnderTest
    private BlogEntryService blogEntryService;

    @InjectIntoMany
    @ObjectUnderTest(implementation = BlogEntryDaoBean.class)
    private BlogEntryDao blogEntryDao;

    @Inject
    private EasyMockProvider mockProvider;

    @Inject
    private Conversation conversationMock;

    @Test
    public void testPersistNewInstance() throws Exception {

        final Long id = databaseRule.getTransactionHelper()
                .executeInTransaction(new Runnable<Long>() {

            @Override
            public Long run(EntityManager entityManager)
                    throws Exception {
                // 1. create a new blog entry
                blogEntryService.newInstance();
                BlogEntry instance = blogEntryService.getInstance();

                entityManager.persist(instance.getAuthor());

                // 2. Set the title and content
                instance.setTitle("title");
                instance.setContent("content");

                // 3. Persist the new blog entry
                blogEntryService.persistOrUpdate();

                return instance.getId();
            }

        });

        BlogEntry blogEntry = databaseRule.getTransactionHelper()
                .loadObject(BlogEntry.class, id);

        Assert.assertNotNull(blogEntry);
    }

    @Test
    public void testGetInstanceById() throws Exception {
        BlogEntry blogEntry = new BlogEntryTestdataBuilder(
                databaseRule.getEntityManager()).buildAndSave();

        blogEntryService.setId(blogEntry.getId());

        BlogEntry instance = blogEntryService.getInstance();

        Assert.assertEquals(blogEntry.getId(), instance.getId());
    }

    @Test
    public void testDelete() throws Exception {
        BlogEntry blogEntry = new BlogEntryTestdataBuilder(
                databaseRule.getEntityManager()).buildAndSave();

        EasyMock.expect(conversationMock.isTransient())
                .andReturn(false).anyTimes();

        mockProvider.replayAll();
        // init instance
        blogEntryService.setId(blogEntry.getId());
        blogEntryService.getInstance();

        blogEntryService.delete();
        mockProvider.verifyAll();

        BlogEntry loadObject = databaseRule.getTransactionHelper()
                .loadObject(BlogEntry.class, blogEntry.getId());

        Assert.assertNull(loadObject);

    }
}

5. Summary / Source Code

Unit testing give us really quick feedback during the development process, because they are executed very fast. With Needle we do not have to write a lot of setup code for our test cases. So, we can focus on the business logic and get easily a high test-coverage of the most important components. With test data builder implementation, changes of the domain model affect only the test data builder implementation and not every test case.

The complete project and all sources of the sample application are available on GitHub.