Monday 14 February 2011

Unit testing managed beans using Mockito

Mockito is a powerful testing framework which is ideal for unit testing managed beans. It allows developers to mock existing classes thereby enabling the behaviour of those classes to be manipulated by the developer depending upon what the aim of the test is.

A simple managed bean which is to be tested is below:

import java.io.Serializable;
import java.math.BigDecimal;
import javax.annotation.Named;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;

@Named("customerManager")
@ApplicationScoped
public class CustomerManager implements Serializable {
    private static final long serialVersionUID = 1L;
    private AccountManager accountManager;

    @Inject
    public void setAccountManager(

        final AccountManager accountManager) {
        this.accountManager = accountManager;
    }

    public boolean isInCredit() {
        boolean inCredit = false;
        BigDecimal currentAccountBalance = 

            this.accountManager.getBalance(AccountType.CURRENT);
        BigDecimal depositAccountBalance = 

            this.accountManager.getBalance(AccountType.DEPOSIT);
        BigDecimal accountBalance = 

            new BigDecimal(currentAccountBalance.doubleValue())
                .add(depositAccountBalance);
        if (accountBalance.compareTo(BigDecimal.ZERO) > 0) {
            inCredit = true;
        }
        return inCredit;
    }

}



The test class for CustomerManager could include various scenarios depending on what is returned by the AccountManager class. An example test class is below:

import java.math.BigDecimal;
import org.junit.Before;
import org.junit.Test;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.times;
import static junit.framework.Assert.assertTrue;

public class CustomerManagerTest {

    private CustomerManager customerManager;
    private AccountManager mockAccountManager = 

        mock(AccountManager.class);

    @Before
    public void setUp() throws Exception {
        customerManager = new CustomerManager();
        customerManager.setAccountManager(mockAccountManager);
    }

    @Test
    public void testPositiveBalance() {
        BigDecimal positiveAmount  = new BigDecimal(1000.00);
        when(mockAccountManager.getBalance(((AccountType)any())))

            .thenReturn(positiveAmount);
        boolean inCredit = customerManager.isInCredit();
        assertTrue(inCredit);
        verify(mockAccountManager, times(2))

            .getBalance((AccountType)any());
    }

}


The when is mocking calls to the AccountManager getBalance method for any given parameter of type AccountType. As the method should have been called twice (once for CURRENT and once for DEPOSIT), the verify call can determine the number of getBalance method invocations. A more fine grained test equivalent of the above could be:

    @Test
    public void testPositiveBalance() {
        BigDecimal positiveAmount  = new BigDecimal(1000.00);
        when(mockAccountManager.getBalance(AccountType.CURRENT))

            .thenReturn(positiveAmount);    
        when(mockAccountManager.getBalance(AccountType.DEPOSIT))

            .thenReturn(positiveAmount);
        boolean inCredit = customerManager.isInCredit();
        assertTrue(inCredit);
        verify(mockAccountManager)

            .getBalance(AccountType.CURRENT);
        verify(mockAccountManager)

            .getBalance(AccountType.DEPOSIT);
    }


The argument matchers are more explicit in the above and could also return different results if needs be.

It is also possible to mock consecutive calls to a method so the same argument can generate a different return object. Using the getBalance example, the first call could return 1000.0 and the next 2000.0.

        BigDecimal positiveAmount1  = new BigDecimal(1000.00);
        BigDecimal positiveAmount2  = new BigDecimal(2000.00);
        when(mockAccountManager.getBalance(((AccountType)any())))

            .thenReturn(positiveAmount1)
            .thenReturn(positiveAmount2);

It is also possible to partially mock real objects using the spy method. If a real AccountManager object had been created and set, then the getBalance method could have be stubbed as shown below:

import java.math.BigDecimal;
import org.junit.Before;
import org.junit.Test;

import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static junit.framework.Assert.assertTrue;

public class CustomerManagerTest {

    private CustomerManager customerManager;
    private AccountManager accountManager = new AccountManager();

    @Before
    public void setUp() throws Exception {
        customerManager = new CustomerManager();
    }

    @Test
    public void testPositiveBalance() {
        AccountManager spyAccountManager = spy(accountManager);
        customerManager.setAccountManager(spyAccountManager);
        BigDecimal postiveAmount  = new BigDecimal(1000.00);
        when(spyAccountManager.getBalance(((AccountType)any())))

            .thenReturn(postiveAmount);
        boolean inCredit = customerManager.isInCredit();
        assertTrue(inCredit);
        verify(spyAccountManager, times(2))

            .getBalance((AccountType)any());
    }

}


There are many more features to the Mockito framework than described here (throwing exceptions, stubbing void methods, etc..) and http://mockito.org/ is a good starting point.

Finally, you can also use annotations to declare mock objects. An alternative to using

    private AccountManager mockAccountManager = 
        mock(AccountManager.class);

would be to use the @Mock annotation as below:

    @Mock
    private AccountManager mockAccountManager;


and in the setup method initialise them by the following command:

    MockitoAnnotations.initMocks(this);



 

1 comment:

  1. Hi, I created a JUnit runner for CDI that has build in support for mockito.

    http://jglue.org/cdi-unit/

    You can just declare your mocks as producer fields, so no need to put set methods on your classes for fields that you want to inject.

    ReplyDelete

Note: only a member of this blog may post a comment.