Sunday, May 10, 2009

Good unit testing with JMock 2

Sometimes it can be difficult to unit test objects like services which can return multiple errors. A good unit testing strategy is to test every part of your code. JMock can help you do that by mocking your objects and code what they will return. You don't need to create your mock objects anymore.

Here's an example.

Account.java :
package com.jmock.vo;

public class Account {

private String id;
private String name;
private boolean activated;

public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isActivated() {
return activated;
}
public void setActivated(boolean activated) {
this.activated = activated;
}
}

AccountServicesImpl.java is the class under test.

AccountServicesImpl.java :
package com.jmock.services.impl;

import org.springframework.beans.factory.annotation.Autowired;

import com.jmock.dao.AccountDAO;
import com.jmock.general.ErrorConst;
import com.jmock.services.exception.AccountServicesException;
import com.jmock.vo.Account;

public class AccountServicesImpl {

@Autowired
private AccountDAO accountDAO; //Spring injected

public Account getAccount(String id) throws AccountServicesException {
Account account = accountDAO.selectAccount(id);

if(account == null)
throw new AccountServicesException(ErrorConst.ERROR_ACCOUNT_UNKNOWN);

if(!account.isActivated())
throw new AccountServicesException(ErrorConst.ERROR_ACCOUNT_INACTIVE);

return account;
}
}

As you can see, there is 3 scenarios to implement :
- The normal test case
- The account unknown test case
- The account inactive test case

And here's the test class.

AccountServicesTest.java :

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import java.lang.reflect.Field;

import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JMock;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.junit.Before;
import org.junit.Test;

import com.jmock.dao.AccountDAO;
import com.jmock.general.ErrorConst;
import com.jmock.services.exception.AccountServicesException;
import com.jmock.services.impl.AccountServicesImpl;
import com.jmock.vo.Account;
import org.junit.runner.RunWith;

@RunWith(JMock.class)
public class AccountServicesTest {

//Mock context
private Mockery context;

private AccountDAO accountDAO;

private AccountServicesImpl underTest;

@Before
public void before() throws Exception {
context = new JUnit4Mockery();
accountDAO = context.mock(AccountDAO.class);

underTest = new AccountServicesImpl();

//We use reflection to access the private field
Field fieldCurrencyServices = underTest.getClass().getDeclaredField("accountDAO");
fieldCurrencyServices.setAccessible(true);
fieldCurrencyServices.set(underTest, accountDAO);
}

@Test
public void testGetAccount() {
final Account account = new Account();
account.setId("124110002055");
account.setActivated(true);

context.checking(new Expectations() {{
oneOf (accountDAO).selectAccount(with(account.getId()));
will(returnValue(account));
}});

try {
underTest.getAccount(account.getId());
} catch (AccountServicesException ex) {
ex.printStackTrace();
fail("No exception expected");
}
}

@Test
public void testGetUnknownAccount() {
final Account account = new Account();
account.setId("124110002055");
account.setActivated(true);

context.checking(new Expectations() {{
oneOf (accountDAO).selectAccount(with(account.getId()));
will(returnValue(null));
}});

try {
underTest.getAccount(account.getId());
} catch (AccountServicesException ex) {
assertEquals(ErrorConst.ERROR_ACCOUNT_UNKNOWN, ex.getErrorId());
}
}

@Test
public void testGetInactiveAccount() {
final Account account = new Account();
account.setId("124110002055");
account.setActivated(false);

context.checking(new Expectations() {{
oneOf (accountDAO).selectAccount(with(account.getId()));
will(returnValue(account));
}});

try {
underTest.getAccount(account.getId());
} catch (AccountServicesException ex) {
assertEquals(ErrorConst.ERROR_ACCOUNT_INACTIVE, ex.getErrorId());
}
}
}

Here's are the dependencies you need to add to your pom.xml
<dependency>
<groupId>org.jmock</groupId>
<artifactId>jmock</artifactId>
<version>2.5.1</version>
</dependency>
<dependency>
<groupId>org.jmock</groupId>
<artifactId>jmock-junit4</artifactId>
<version>2.5.1</version>
</dependency>

5 comments:

  1. Just want to add a usefull link which is a link on the JMock documentation and more specifically on the cardinality part. Indeed you can use not only oneOf but atLeast(n).of, exactly(n), etc...

    http://www.jmock.org/cardinality.html

    ReplyDelete
  2. Don't forget the scope in the maven file :
    <scope>test</scope>

    ReplyDelete
  3. You can replace the reflection access by:
    org.apache.commons.beanutils.BeanUtils.setProperty(underTest, "accountDao", accountDao);

    why reinvent the whell ?)

    ReplyDelete
  4. thanks for posting!

    ReplyDelete