EasyMock Tutorial

I'm writing this tutorial because I never really got one when I had to deal with this little testing suite and I wish I had. I tried going off of existing examples from the jobs where I had to use it and that didn't go as well as anyone thought it would. I tried asking people for help and they didn't have the time, patience, or (sometimes) knowledge to help me. I tried reading the documentation (I KNOW! Who does that??), but it didn't really make any sense. I don't want anyone else to waste that much time at the beginning of their job on learning this stuff, so here we go. A quick tutorial on using EasyMock with JUnit.

First off, do you know how to use JUnit? The documentation on that is pretty simple. Go check it out. Also, make sure you have the latest version of Java.

Ok, cool. Now we need some classes to test. We should have more than one, and we should also make sure they interact a bit.

public interface WhateverInterface(){
   public void doSomethingCrazy(String string);
   public int canIGetANumber();
   public void readThisFile(java.io.File file);
   public void doOtherWork();
}

public class CouldaHadClass{
   private String string;
   public CouldaHadClass(String itsAString){
      string = itsAString;
   }

   public int doWhatever(WhateverInterface inter){
      inter.doSomethingCrazy(string);
      int x = inter.canIGetANumer();
      for(int i = 0;i < x;i++){
         inter.readThisFile(new java.io.File("CouldaHadClass.class")); //should always be there
      }
      if(x == 5){
         inter.doOtherWork();
      }
      return x;
   }
}

Those are some nice simple (but not too simple) interactions. Let's get to testing!

Our test class will need to be able to access both of these classes, so import or package as needed. For EasyMock we will need to add the line:

import static org.easymock.classextension.EasyMock.*;

We use the classextension because it does everything that the regular EasyMock classes do, but it also lets us mock interfaces. Now we can make a test method.

@Test
public void TestClass(){

We start by making a mocked object of our interface:

WhateverInterface whatev = createMock(WhateverInterface.class);

We can't just throw that into the thick of things just yet. We have to do what the EasyMock documentation calls "recording". I prefer to call it "scripting" because we are basically telling the EasyMock class what will be happening to it as we go through the test.

We have to decide what we want to do with it before we can figure out what will happen to it, so let's make up a little test.

String testStr = "testing123";
CouldaHadClass clazz = new CouldaHadClass(testStr);
int ret = clazz.doWhatever(whatev);

There, that was easy. But now how do we actually test the behavior? Well we have to tell whatev what is supposed to happen to it when this code runs. So let's investigate.

The first time that something happens to whatev, it is passed into the doWhatever method. When it goes in there, doSomethingCrazy is called with the String that was passed into the CouldaHadClass object. Luckily, we saved that as a variable (which you should just always do in your tests), so we can use it. After we make that String and the mock WahteverInterface in our tests we add a line that says "doSomethingCrazy will be called on the WhateverInterface with the argument 'testing123'":

whatev.doSomethingCrazy(testStr);

Yeah, that's how easy it is for void methods. Isn't that crazy? The next thing that happens to whatev is its canIGetANumber() method is called. But wait...we haven't defined that method. What will it return to CouldaHadClass? Don't worry, friend; we have control. We what it to return something that will do something else down the line, so it should return 5 (see the "x == 5" if statement above). To control return values (and even Exception throwing) we have to use the expect method from EasyMock:

int x = 5; //we will need this later
expect(whatev.canIGetANumber()).andReturn(x);

I know, it looks crazy doesn't it? So we told it to expect a call to canIGetANumber() on whatev and to return 5 to whoever called it. If you expect a method call to happen more than once and you want to return the same thing each time you can add a line like this:

expect(whatev.canIGetANumber()).andReturn(x).times(4);

If you want different return values, add a line like this:

expect(whatev.canIGetANumber()).times(4).andReturn(1).andReturn(2).andReturn(3).times(2);

That line will return 1 to the first call to canIGetANumber(), 2 to the second call, and 3 to the last two calls. Now back to our example. You can also use anyTimes() to specify that a method could be called any number of times (including 0), atLeastOnce() if you expect it to be called any number of times greater than 0, or times(min, max) to give a valid range for the number of times it can be called.

To throw an Exception from a method add a line like this:

expect(whatev.canIGetANumber()).andThrow(new WhateverException("whatever"));

This can be added anywhere an andReturn is added and the order of returns and throws will be preserved.

After that canIGetANumber() business, we expect to see a call to readThisFile x times (see the for loop?). That method takes a File object that we don't have access to so we just have to tell it that it will be given A file:

whatev.readThisFile(isA(File.class));
expectLastCall().times(x);

Because it's a void method, we have to use expectLastCall() here so we can specify that it will be called x times (void methods can't have methods called on their return values).

The isA method basically does an instanceof check on whatever comes in for that parameter, and verification (coming later) will fail if the types don't match. If there is more than one parameter for a method and you use isA on one, you have to use isA on all of them (I know, it's annoying). There are also boolean methods you can use similarly to isA like and, or, not, and comparisons to try to narrow things down. Look at the static methods in the EasyMock class to see where you might be able to use them.

After that, the doOtherWork() method will be called because x is indeed 5. So we add that expectation:

whatev.doOtherWork();

No expect needed because it doesn't need to return anything, and it won't be called more than once.

That's all the stuff we need to worry about with whatev, so we can "replay" the "recording" (or "script" like I still like to call it):

replay(whatev);

This signifies that this object is safe to use, and is ready to be monitored by EasyMock. After that we can send it into the doWhatever method. Now that we know what doWhatever will return (x, or 5), we can test for that by adding this line to the end of the test just like regular JUnit:

assertEquals(x, ret);

After that we need to verify that our script was followed by adding this line:

verify(whatev);

If you have more mock objects you can comma separate them in that method call. That's it for that test. Here it is all put together:

@Test
public void TestClass(){
   WhateverInterface whatev = createMock(WhateverInterface.class);
   String testStr = "testing123";
   whatev.doSomethingCrazy(testStr);
   int x = 5; //we will need this later
   expect(whatev.canIGetANumber()).andReturn(x);
   expect(whatev.readThisFile(isA(File.class))).times(x);
   whatev.doOtherWork();
   replay(whatev);

   CouldaHadClass clazz = new CouldaHadClass(testStr);
   int ret = clazz.doWhatever(whatev);
   assertEquals(x, ret);
   verify(whatev);
}

The order of our script is important here as well as the number of calls. If anything is out of order or a different number of calls are made, the verify line will fail. If any extra methods are called, an AssertionError will be thrown from them. To avoid this, use createNiceMock instead of createMock (it will return 0, null, or false from those methods). If you want to make order not matter (if, for instance, you don't know or care in what order methods are called), add a line like this:

checkOrder(mockObject, false);

The test we made could be changed by changing x to something else. If that happened, doOtherWork would not be called, so it would need to be removed from the scripting section.