This is a blog about strategies and tactics I have learned, mostly from my experience of playing 3D fighting games and reading classic strategy books. Oh... and Java.

Tuesday, March 10, 2009

What are mock objects, why do I love them, and why do I use Mockito?

What's a mock object? Here's Wikipedia's definition:
In object-oriented programming, mock objects are simulated objects that mimic the behavior of real objects in controlled ways. A computer programmer typically creates a mock object to test the behavior of some other object
When you write a unit test, you want to try and focus on testing a single class. But, in any complex system, classes use other classes. So, in that situation, how do you just test one class? You separate class creation from class usage: The class you're trying to test never contains the "new" keyword. Instead, it receives the classes it depends on through it's constructor or setters (the formal term for this is Dependency Injection). So your code that used to look like this:

public void useDependency() {
new Dependency().useDependency();
}
Now looks like this:

public void setDependency(Dependency dependency) {
this.dependency = dependency;
}

public void useDependency() {
this.dependency.useDependency();
}
Yes, there's a little bit more code, but now it's easy to test.

In Michael Feather's book, Working Effectively With Legacy Code, he describes the difference between a Mock and a Fake object. A mock object verifies its own state is correct. A fake object, on the other hand, has its state tested by an external object (typically the testcase it's created in).

Before I discovered Mockito, I got by with Fake objects. Fake objects have some problems though. The main problem is you have to make them yourself. If you need a fake interface, but you're only testing one method, you still have to manually stub out all the other methods.

For example, I'm making a Swing app and when I wrote test cases for my client, I had to make a fake Graphics object. With just stub methods, this class is about 300 lines!

So then I tried JMockit, but I had some major complaints. It's tutorial is horrid and scattered all over the place. Whenever I tried to use it, I always had to open its documentation after realizing I wasn't using it correctly. What makes this worse is it's not easy to discover you're using it incorrectly! If you do something wrong, it'll usually return null instead of throwing an exception. 99% of the time, you pass this null pointer into a setter and you eventually get a NullPointerException in the middle of your test. Why doesn't it fail immediately? Also, it seems like you have to use different API calls for mocking concrete classes vs interfaces (or maybe it doesn't work with concrete classes. Here's a fun exercise: See if you can figure that out on your own. See what I mean by the horrid documentation?). Plus, you need special VM arg to run it.

JMockit has one redeeming quality (that's actually very cool): It lets you inject classes even if you're not using dependency injection. At first that may not seem possible, but check out its "documentation" to see how. For this reason, I would still use this library if I was trying to test crappy legacy code that I was terrified to change in any way.

Next, I looked at EasyMock for about 3 minutes. I couldn't figure it out with that amount of time so I moved on (I know, not a fair review, but at least an honest one).

Then, by luck, I stumbled upon this controversial, unrelated article and in a comment I saw a reference to Mockito. I checked it out and it turns out to be the best of all worlds. It saves you from writing fakes, it's documentation/API is concise, consistent and without ambiguity, it doesn't require extra VM arguments, and, you can learn it in under 3 minutes.

I highly recommend it to anyone who's writing unit tests. It saves me a lot of time while simultaneously making my code more readable.

BTW, if you're already using EasyMock, here's a Mockito article on Mockito VS EasyMock.

6 comments:

Unknown said...
This comment has been removed by the author.
Unknown said...

Mockito is great! Check out the slides I put up on its basic usage.

http://www.rapaul.com/2008/11/19/mocking-in-java-with-mockito/

Brambo said...

After using Easymock and hitting it's limits (lack of mocking static and final classes/methods), I've started to use PowerMock http://code.google.com/p/powermock . It extends EasyMock and Mockito.

I recommend it to anyone who starts mocking or is used to work with Easymock.

According to the author:
"PowerMock is not intended to replace other frameworks, rather it can be used in the tricky situations when other frameworks does't allow mocking. PowerMock also contains other useful features such as suppressing static initializers and constructors."

Kind regards,
Bram Bruneel

Steve Freeman said...

Mockito has some very cool ideas, but I think you've missed some of the point of Mocks Objects.

The significant intent of Mocks is to check how the object under tests relates to its collaborators. A Mock doesn't necessarily check its own state, it checks how it is called.

You might want to look into some of the deeper ideas that go with the technique. There's a work in progress description at http://www.mockobjects.com/book

Toolman said...

Great to see you seeing the benefits of mocking, but I agree with SteveFreeman.

Consider the mocks as the supports to the real object in test. Mocks allow you avoid having other real objects that are not related to the test in question.

Mocks simulate the expected behavior of external objects the the object(s) being tested.

Date Hater said...

Hi Steve Freeman,

I agree with you and that was what I meant to convey but I guess I didn't do it very well. When you consider a Fake object, you've got to implement many things on your own for the purposes of your test: If you want to check how many times a method was called, you have to increment a field. If you want to check the correct parameters were passed, you've got to save the parameters in fields. After you execute the method under test, you check the Fake's STATE to make sure your class collaborates correctly and called the correct methods.

So, when I used the word state, I meant how it is called.