Welcome to the third and final installment in my EasyMock Fundamentals series, where I take you through the ins and outs of the open source Java library. EasyMock can be used in conjunction with a unit testing framework, such as JUnit or TestNG, to facilitate better unit testing without all the 2nd degree dependency issues. EasyMock provides the means (through the use of mock objects) to specify the response that will be returned when the code being tested makes calls to external methods. This will allow the result of the unit test to be isolated from variables outside of the scope of the unit test code.

Argument Matching

Argument matching is related to setting up expectations and is one of those subtleties of EasyMock that you might miss and certainly not understand until you really start writing tests.

The following shows setting up an expectation without any argument matchers:

EasyMock.expect(mockEmailer.sendEmail("foodaddy")).andReturn(true);

In this scenario, you’re telling EasyMock to return true when sendEmail() is called with "foodaddy" as the parameter. Simple enough, but what if you wanted tell EasyMock to return true for all parameter values, or only certain parameter values, or only on Tuesdays, etc? This is where argument matchers come to play.

The following demonstrates an argument matcher that matches all String values:

EasyMock.expect(mockEmailer.sendEmail(EasyMock.isA(String.class))).andReturn(true);

What about all String values except "foodaddy"?

EasyMock.expect(mockEmailer.sendEmail(EasyMock.not(EasyMock.eq("foodaddy")))).andReturn(true);

EasyMock supports a wide selection of argument matchers that can be used to create any type of matcher. Here are some of the matcher methods:  anyInt(), anyObject(), contains(), eq(), geq(), gt(), isA(), leq(), lt(), same(), startsWith().

Argument matchers can be combined using the following methods:  and(), or(), not()

Consult the API for a complete listing, but as an example:

// true for "foodaddy" or "foo.*daddy"
EasyMock.expect(mockEmailer.sendEmail(
          EasyMock.or(EasyMock.eq("foodaddy"),
                      EasyMock.and(EasyMock.startsWith("foo"),
                                   EasyMock.endsWith("daddy")))
                      )).andReturn(true);

EasyMock even has support for creating custom argument matchers. Consult the API for more info. For more info on argument matchers, see this blog post.

Argument Matching — Part 2

EasyMock has this annoying feature/limitation that prevents mixing non-argument matchers (raw values) with argument matchers when setting up expectations. I often encounter this when trying to setup expectations for a method that takes multiple parameters where some are simple primitives and others are complex types. This can be annoying because often times you want to be able to setup your expectations in this way. Given the following:

public interface Emailer {
  boolean sendEmail(String emailAddress, String message);
}
public class Foo {
  private Emailer emailer;
  public Foo(Emailer emailer) {
    this.emailer = emailer;
  }
  public void doSomethingCool(boolean useDefault, String emailAddress) {
    String message = null;
    if (useDefault) {
      message = "default message";
    }
    else {
      message = "something else";
    }
    emailer.sendEmail(emailAddress, message);
  }
}

So now you want to test this code so you start as follows:

public class TestFoo {
  @Test
  public void test_doSomethingCool()
  {
    final String emailAdrs = "foo@foodaddy.com";
    Emailer mockEmailer = EasyMock.createMock(Emailer.class);
    // return true when emails are sent to foo@foodaddy.com regardless of the message being sent
    EasyMock.expect(mockEmailer.sendEmail(emailAdrs, EasyMock.isA(String.class))).andReturn(true);
    EasyMock.replay(mockEmailer);
    Foo myFoo = new Foo(mockEmailer);
    myFoo.doSomethingCool(true, emailAdrs);

    EasyMock.verify(mockEmailer);
  }
}

As the comment above states, you don’t care what message is being sent to “foo@foodaddy.com“. You want all emails sent to "foo@foodaddy.com" to return true. Seems valid enough, but when you run your test, you’ll get an error message similar to this:

java.lang.IllegalStateException: 2 matchers expected, 1 recorded. This exception usually occurs when matchers are mixed with raw values when recording a method: You need to use no matcher at all or a matcher for every single parameter:

Huh? The key is “…_matchers are mixes with raw values_…”. Unfortunately, you can’t do what you were hoping to do. EasyMock requires that you use ALL raw values or ALL argument matchers in a given expectation. So the following shows how you accomplish this:

public class TestFoo {
  @Test
  public void test_doSomethingCool()
  {
    final String emailAdrs = "foo@foodaddy.com";
    Emailer mockEmailer = EasyMock.createMock(Emailer.class);
    EasyMock.expect(mockEmailer.sendEmail(EasyMock.eq(emailAdrs), EasyMock.isA(String.class))).andReturn(true);
    EasyMock.replay(mockEmailer);
    Foo myFoo = new Foo(mockEmailer);
    myFoo.doSomethingCool(true, emailAdrs);
    EasyMock.verify(mockEmailer);
  }
}

The eq() argument matcher is what’s needed here and is especially handy at matching primitive values. The eq() argument matcher can certainly be used to match more complex types, but with primitives, you don’t have to worry about the implementations of equals() and hashCode() as discussed next.

Captures

These are cool, but the documentation and examples I’ve found are not. I like examples so hopefully the following will demonstrate Capture‘s coolness. Given the following:

public class Recipient {
  private String name;
  private String email;
  public Recipient(String name, String email) {
    this.name = name;
    this.email = email;
  }
  // getters for above attrs
}
public interface Emailer {
  boolean sendEmail(Recipient r, String message);
}
public class Foo {
  private Emailer emailer;
  public Foo(Emailer emailer) {
    this.emailer = emailer;
  }
  public void doSomethingCool(boolean useDefault) {
    Recipient r = null;
    if (useDefault) {
      r = new Recipient("foodaddy", "foo@part.net");
    }
    else {
      r = new Recipient("noreply", "noone@part.net");
    }
    emailer.sendEmail(r, "some message");
  }
}

So the question is, how would you test the doSomethingCool() code and ensure that the if/else conditional evaluation is being done correctly? Granted, this is a simple example, but the question is still valid. There are 2 possibilities.

  • 1) Implement equals() and hashCode() on the Recipient object so that their implementation, specifically equals(), could be used in your unit test as follows.
public class TestFoo {
  @Test
  public void test_doSomethingCool()
  {
    {
      Recipient recOne = new Recipient("foodaddy", "foo@part.net");
      Emailer mockEmailer = EasyMock.createMock(Emailer.class);
      // this next line is the key - we're relying on the fact that recOne.equals(recipient) == true - where recipient is the one created in doSomethingCool()
      EasyMock.expect(mockEmailer.sendEmail(recOne, ...)).andReturn(true);
      EasyMock.replay(mockEmailer);
      Foo myFoo = new Foo(mockEmailer);
      myFoo.doSomethingCool(true);
      EasyMock.verify(mockEmailer);
    }
    {
      Recipient recTwo = new Recipient("noreply", "noone@part.net");
      Emailer mockEmailer = EasyMock.createMock(Emailer.class);
      // same comment as before
      EasyMock.expect(mockEmailer.sendEmail(recTwo...)).andReturn(true);
      EasyMock.replay(mockEmailer);
      Foo myFoo = new Foo(mockEmailer);
      myFoo.doSomethingCool(false);
      EasyMock.verify(mockEmailer);
    }
  }
}

As was noted in the code, the key is that we’re relying on the implementation of Recipient.equals().

 

On the test code line:

mockEmailer.sendEmail(recOne, ...);

two things are occurring:

  1. You are setting up an expectation that the sendEmail() method will be called
  2. The Recipient that is passed to sendEmail() will be equal to the one provided.

Even though the same Recipient object instance (recOne != recipient) is not used in the expectation, the two different instances are considered equal:

recOne.equals(recipient) == true

By not including an argument matcher explicitly, EasyMock is using object equality (equals()) to match. This is the same as using the eq() argument matcher.

While this approach is certainly valid, it’s less than ideal because it requires that equals() be implemented. Furthermore, the implementation of equals() has to be known and understood how to be taken advantage of (i.e. you have to know that name and email are used in equals() rather than something like an id attribute). Additionally, you’re not able to actually test/assert against the attribute values of the created Recipient. That’s essentially what’s being done by taking advantage of the implementation of equals(), but it’s less explicit and obvious to the poor maintenance developer.

  • 2) The second (and better) approach is to use an EasyMock Capture as follows to retrieve, err capture, the actual instance of the Recipient object created by the doSomethingCool() method.
public class TestFoo {
  @Test
  public void test_doSomethingCool()
  {
    {
      Emailer mockEmailer = EasyMock.createMock(Emailer.class);
      Capture rCap = new Capture();
      EasyMock.expect(mockEmailer.sendEmail(EasyMock.capture(rCap), ...)).andReturn(true);
      EasyMock.replay(mockEmailer);
      Foo myFoo = new Foo(mockEmailer);
      myFoo.doSomethingCool(true);
      EasyMock.verify(mockEmailer);
      Recipient recipient = rCap.getValue();  // actual Recipient instance returned here
      Assert.assertEquals(recipient.getName(), "foodaddy");
      Assert.assertEquals(recipient.getEmail(), "foo@part.net");
    }
    {
      Emailer mockEmailer = EasyMock.createMock(Emailer.class);
      Capture rCap = new Capture();
      EasyMock.expect(mockEmailer.sendEmail(EasyMock.capture(rCap), ...)).andReturn(true);
      EasyMock.replay(mockEmailer);
      Foo myFoo = new Foo(mockEmailer);
      myFoo.doSomethingCool(false);
      EasyMock.verify(mockEmailer);
      Recipient recipient = rCap.getValue();  // actual Recipient instance returned here
      Assert.assertEquals(recipient.getName(), "noreply");
      Assert.assertEquals(recipient.getEmail(), "noone@part.net");
    }
  }
}

Using a Capture has none of the requirements/drawback/quirks as the first approach and is certainly no harder to use.

Links

 


This is the last in my three-part EasyMock Fundamentals series. Please leave questions and comments in the comments section below. If you missed them, be sure to take a look at EasyMock Fundamentals — Part 1 and EasyMock Fundamentals — Part 2.