JBoss Blog

March 8, 2012 Previous day Next day

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.

 

Needle is a lightweight framework for testing Java EE components in isolation outside of the container. Objects under test will get their dependencies injected automatically. You are free to supply implementations yourself or rely on mock objects provided by Needle.

 

Needle is fully extensible, you may implement your own injection providers or register additional annotations, e.g for Seam 2 injection.

 

 

Example Seam component:

 

@Name(AuthenticatorService.NAME)
@Stateless
public class AuthenticatorServiceBean implements AuthenticatorService {

    @Logger
    private Log log;

    @In
    private Credentials credentials;

    @In 
    private Events events:

    @EJB
    private UserDao userDao;

    @Out(required = false, scope = ScopeType.SESSION)
    private User currentUser;

    @Override
    public boolean authenticate() {
        log.info("authenticate user #0", credentials.getUsername());
        User user = userDao.findByUsername(credentials.getUsername());

        if (user != null && user.verifyPassword(credentials.getPassword())) {
            currentUser = user;

            events.raiseEvent("authenticate", user);

            return true;
        }

        return false;

    }
}

 

 

 

The following example demonstrates hwo to write a simple  Needle test. Our test based on JUnit 4 and the EasyMock framework. EasyMock is a framework to create Mock Objects to test components in isolation. Needle provides JUnit Rules to extend JUnit. Rules are basically wrappers around test methods. They may execute code before, after or instead of a test method.

 

public class AuthenticatorServiceBeanTest {

    @Rule
    public NeedleRule needleRule = new NeedleRule();

    @ObjectUnderTest
    private AuthenticatorServiceBean authenticator;

    @EJB
    private UserDao userDaoMock;

    private EasyMockProvider mockProvider = needleRule.getMockProvider();

    @InjectIntoMany
    private Credentials credentials = new Credentials();

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

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

        mockProvider.verifyAll();
    }

    @Test
    public void testLoginWithWrongPassword() throws Exception {

        String username = "username";

        credentials.setUsername(username);
        credentials.setPassword("any");

        mockProvider.resetToStrict(userDaoMock);

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

        mockProvider.replayAll();
        boolean login = authenticator.authenticate();
        Assert.assertFalse(login);
        mockProvider.verifyAll();

    }

    @Test
    public void testLoginSuccess() throws Exception {
        String username = "username";
        credentials.setUsername(username);
        credentials.setPassword("secret");

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

        mockProvider.replayAll();
        boolean login = authenticator.authenticate();
        Assert.assertTrue(login);
        mockProvider.verifyAll();
    }
}

 

 

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. By default, Mock objects for the dependencies are created and injected.

 

The following configuration shows the registration of Seam 2 annotations in the needle.properties.  Needle will look for the configuration somewhere in the classpath.

 

custom.injection.annotations=org.jboss.seam.annotations.In,org.jboss.seam.annotations.Logger

 

 

If you are using Maven as your build tool add the following  dependencies to your pom.xml file to get started with Needle:

 

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

 

We need the following dependecies to use Needle with JUnit and EasyMock. You are free to use other Test- and Mock-Frameworks, such TestNG with Mockito.

 

<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>

 

 

The complete example is available with our Seam Archetype.

 

mvn archetype:generate \
   -DarchetypeArtifactId=jbosscc-seam-archetype \
   -DarchetypeGroupId=de.akquinet.jbosscc \
   -DarchetypeVersion=1.3

 

 

More examples and documentation about Needle are available on the Needle project page: http://needle.spree.de.