Account.java
package learn.mockito.bank; public class Account { private int id; private int balance; public Account(int id, int initBalance) { this.id = id; this.balance = initBalance; } public int getId() { return id; } public void setBalance(int balance) { this.balance = balance; } public int getBalance() { return balance; } }
AccountService.java
package learn.mockito.bank;
public interface AccountService {
public Account getAccount(int id);
public void updateAccount(Account account);
}
TransactionManager.java
package learn.mockito.bank; public class TransactionManager { private AccountService accountService; public TransactionManager(AccountService accountService) { this.accountService = accountService; } public void transferMoney(int senderID, int receiverID, int amount) { Account sender = accountService.getAccount(senderID); Account receiver = accountService.getAccount(receiverID); if (sender.getBalance() >= amount ) { sender.setBalance(sender.getBalance() - amount); receiver.setBalance(receiver.getBalance() + amount); accountService.updateAccount(sender); accountService.updateAccount(receiver); } else { throw new IllegalArgumentException("Sender's balance is not sufficient"); } } }In this application AccountService defines the interface for the database transactions. Consider a scenario you need to test TransactionManager at this stage. With the normal approach without mocking it might seem impossible to write a unit test for TransactionManager without an implementation of AccountService. Although any implementation is available then the test depends on the facts such as database connectivity, etc. This reduces the loosely coupled unit tests for TransactionManager class. These kind of troubles in unit tests can be eliminated by using mock objects in your tests. For example the database transactions mentioned above can be mimic using a collection such as Map. Then you can return balance from your data collection and update the collection for update calls. This reduces the test failures and coupling of your tests. Then you can use your unit tests for test the validity of you intended methods despite the changes to the others. In this approach creating mock objects is an additional overhead and it introduces more classes and methods which make things more complex. Mocking frameworks such as Mockito, EasyMock come into play and make our life easier. Lets now try to write a Unit Test for the TransactionManager class with the support of Mockito. The code shown below is a parameterized Unit Test which test transferMoney() method with different values for sender, receiver and amount transferred. You can defin any number of value sets in getParameters() method. This is not related to Mockito and you can write a ordinary test instead and a brief explanation on writing a parameterized test is discussed at the end of this post.
AccountManagerTest,java
package learn.mockito.bank; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import java.util.Arrays; import java.util.Collection; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(value = Parameterized.class) public class AccountManagerTest { private int senderBalance; private int receiverBalance; private int transferAmount; @Parameters public static Collection<Integer[]> getParameters() { return Arrays.asList(new Integer[][]{ {1000, 1000, 500}, {1500, 2000, 1000}, {3000, 4000, 2000} }); } public AccountManagerTest(int senderBalance, int receiverBalance, int transferAmount) { this.senderBalance = senderBalance; this.receiverBalance = receiverBalance; this.transferAmount = transferAmount; } @Test public void testTransferMoney() { Account sender = new Account(1, senderBalance); Account receiver = new Account(2, receiverBalance); AccountService as = mock(AccountService.class); when(as.getAccount(1)).thenReturn(sender); when(as.getAccount(2)).thenReturn(receiver); TransactionManager tm = new TransactionManager(as); tm.transferMoney(1, 2, transferAmount); Assert.assertEquals(sender.getBalance(), senderBalance - transferAmount); Assert.assertEquals(receiver.getBalance(), receiverBalance + transferAmount); } }As dependencies I have used java libraries.
- mockito-all-1.9.5
- junit-4.11
Creating a parameterized test. You can create Unit Tests with more than one input parameter set for a particular test. These are known as parameterized tests. In order to create a parameterized test, the following conditions must be satisfied.
- Annotate the test class with @RunWith annotation with Parameterized.class as the value.
- Define instance variables which are used in tests.
- Define a public static method which is annotated with @Parameters. This is a no argument method and return type of it is java.util.Collection. This method must return your test parameter as an Arrays of identical length.
- Declare a public constructor for test class which accepts the number of arguments returned by parameter method and instantiate instance variables.