Testing using mocks
Mock objects are very useful if used right way. I shared some of the experience of using Mock Objects in need-driven-software-development-using post.
In this post i share 2 things
– Contract based testing using mocks.
– Patterns to organized mock code.
Contract based testing
Lets take scenario where you are building Money remittance service. Key component in such type of service is Currency Converter , Bank Service & FX Service.
50000 feet design of fictitious forex service will look something like below.
We have to write FX Service that needs Currency converter & Bank Transfer service.
This is perfect scenario for contact based testing.
Code snippet for FXService
public class FXService { private final CurrencyConverter currencyConverter; private final BankService bankService; private final double commissionPer; public String transfer(Money money, BankAccount destinationAccount, Currency target) { String sourceCurrency = money.currency().name(); String targetCurrency = target.name(); double commissionAmount = calculateCommission(money.amount()); double fxRate = currencyConverter.convert(1, sourceCurrency, targetCurrency); // First interaction double transferAmount = calculateTransferAmount(money, commissionAmount); double totalAmount = applyFxRate(transferAmount, fxRate); String transactionId = bankService.deposit(totalAmount, destinationAccount); // Second interaction return transactionId; } }
Our new FX service has to follow below contract
- Interact with currency converter & Bank Transfer based on input/output contract.
- Makes 1 call to each of service.
One way to test FX service is to call the real service but that means slow running test and dependency on service that it has to up whenever our test is executing. Sometime calling real service is not an option because it is not developed yet.
Smart way is to mock these collaborator( Currency Converter & Bank Transfer) and verify interaction using mocking framework.
Another advantage of testing with mocks that it enables to verify that both currency & bank transfer service are used by fxservice in expected way.
Lets look at mock based test.
@Test public void transfer_sgd_to_inr() { FXService fxService = new FXService(currencyConverter, bankService, 0.0d); BankAccount account = new BankAccount("1111-22222", "SuperStableBank"); expect(currencyConverter.convert(1, "SGD", "INR")).andReturn(50d); expect(bankService.deposit(100d, account)).andReturn("99999"); replay(currencyConverter, bankService); String id = fxService.transfer(new Money(SGD, 2d), account, INR); assertEquals("99999", id); verify(currencyConverter, bankService); }
This test is written using EasyMock framework and is mocking reply from collaborators.
Write the test that you want to read
One of the important property of good test is that it is enjoyable to read.
Mocks can make this goal more difficult to achieve as setup code for unit test will have very complex assembling logic that will be mix of some normal object set and some mocking expectation. I am sure you have seen before function in test that is used as dumping ground for setup required for all the tests in class.
Lets look at some mock code we used earlier and try to improve it
expect(currencyConverter.convert(1, "SGD", "INR")).andReturn(50d); expect(bankService.deposit(100d, account)).andReturn("99999"); replay(currencyConverter, bankService);
Another way
@RegisterExtension JUnit5Mockery context = new JUnit5Mockery(); context.checking(new Expectations() {{ oneOf(currencyConverter).convert(1, "SGD", "INR"); will(returnValue(50d)); oneOf(bankService).deposit(100d, account); will(returnValue("99999")); }});
Both of the above code is doing same thing but later one which is written with jmock has nice sugar method to express same thing.
This helps in keeping expectation clean and in context with code that is being tested. Collaborator object in the context are mocked out.
Simple pattern but very effective in making test readable.
Code used in this post is available on github
Published on Java Code Geeks with permission by Ashkrit Sharma, partner at our JCG program. See the original article here: Testing using mocks Opinions expressed by Java Code Geeks contributors are their own. |