Links

  • 1. Sogeti
  • 2. JBoss
  • 3. IBM
  • 4. Oracle
  • 5. SpringSource
  • 6. NL-JUG
  • 7. Java

Archives

Syndication  RSS 2.0

RSS 1.0
RSS 2.0

Bookmark this site

Add 'JCN Blog' site to delicious  Add 'JCN Blog' site to technorati  Add 'JCN Blog' site to digg  Add 'JCN Blog' site to dzone

Posted by Patrick Kik at 11:42 on Thursday 27 February    Add 'Custom argument matching with EasyMock' site to delicious  Add 'Custom argument matching with EasyMock' site to technorati  Add 'Custom argument matching with EasyMock' site to digg  Add 'Custom argument matching with EasyMock' site to dzone

EasyMock is a very useful framework when creating unittests. One particular useful feature is the ability to expect the method arguments:

accountService.deleteAccounts(EasyMock.notNull());

But what if these out of the box expectations are not enough? You write your own argument matcher!


Let’s say we have a banking application with an Account class that hold an account number:

/**
 * Account information. Just an account number to keep it simple.
 */
public class Account {
    private int number;
    public Account(int number) { this.number = number; }
    public int getNumber() { return number; }
}

Our application needs to be able to delete accounts. For that there is an AccountController:

/**
 * First layer of logic.
 */
public class AccountController {
    private AccountService service;

    /**
     * Will delete accounts. Account numbers up to 1000 are special and must never be deleted.
     * @param accounts List of accounts to delete.
     */
    public void deleteAccounts(List<Account> accounts) {
        List<Account> accountsToDelete =
            accounts.parallelStream()
                .filter(account -> account.getNumber() >= 1000)
                .collect(Collectors.toList());
        service.deleteAccounts(accountsToDelete);
    }

    public void setService(AccountService service) { this.service = service; }
}

Notice that the controller holds a reference to a service and that our controller does some filtering on accounts. Accounts with an account number under 1000 are special en must never be deleted.

So the controller receives a collection of accounts, removes any special accounts and hands the collection of normal accounts over the the AccountService which will perform the actual deletion.

AccountService is just an interface. In this example no implementation is needed:

/**
 * Account business logic.
 */
 public interface AccountService {

    /**
     * Will delete accounts.
     * @param accounts Accounts to delete.
     */
    void deleteAccounts(List<Account> accounts);
}

Now we have to write a unittest to check if our special account filter is correct:

/**
 * Unittest to check filter that special accounts must not be deleted.
 */
public class AccountControllerTest {

    @Test
    public void testFilter() {
        // Mock the service.
        AccountService service = EasyMock.createStrictMock(AccountService.class);

        // Expect that deleteAccounts will be called.
        // What we expect as an method parameter we will test in method eqAccountsToDelete.
        service.deleteAccounts(eqAccountsToDelete());

        EasyMock.replay(service);

        AccountController controller = new AccountController();
        controller.setService(service);

        // Two special accounts (< 1000), two normal accounts.
        List<Account> accounts = new ArrayList<>();
        accounts.add(new Account(55));
        accounts.add(new Account(999));
        accounts.add(new Account(1_000));
        accounts.add(new Account(9_878_972));

        controller.deleteAccounts(accounts);

        EasyMock.verify(service);
    }

    private List<Account> eqAccountsToDelete() {

        EasyMock.reportMatcher(new IArgumentMatcher() {
            @Override
            public boolean matches(Object object) {
                List<Account> accounts = (List<Account>) object;
                // Checks if every account is a normal account.
                return accounts.parallelStream()
                    .allMatch(account -> account.getNumber() >= 1000);
                }

                @Override
                public void appendTo(StringBuffer stringBuffer) {
                    stringBuffer.append("no special accounts");
                }
        });

        return null;
    }
}

In this simple example it would be easy to define a list with the expected accounts and use that as an expectation for the deleteAccounts method. But in more complex tests is could happen that it is hard to predict the exact arguments or the arguments are large objects and you actually wish to check just a small part of it.

The key is to use the EasyMock interface IArgumentMatcher. It will provide you with the actual argument that is passed into the mock method. You can run whatever check you want.


© 2019 Java Competence Network. All Rights Reserved.