Getting Started
JMockIt is an interesting mocking library that actively does quite a bit of work for you if you simply configure your classpath correctly and use annotations. For this example I’ll use the Login Service from the Mockito tutorial I wrote.
-
First you’ll need to [[http://code.google.com/p/jmockit/downloads/list download JMockIt]. As of this writing I used 0.999.8, which was “featured” at the time. - Next, extract the downloaded zip somewhere.
- You’ll need to include jmockit.jar in your classpathas the first entry
For Eclipse Here’s what I did in Eclipse
- Create a standard Java project
- Create a new folder in the project called libs
- Copied the contents of the jmockit folder into the libs directory
- Add
/libs/jmockit/jmockit.lib to the class path (Project:Properties:Java Build Path:Libraries:Add Jar - While you are there you can add JUnit 4 to your project as well by usingAdd Library…
- Make jmockit.lib the first entry in the class path (Project:Properties:Java Build Path:Order and Export - select jmockit.jar and move it up to the top of th elist)
The Login Service initially uses 4 java source files:
- LoginServiceShould.java - a series of unit tests
- LoginService.java - the class under test
- Account - an interface
- AccountRepository - an interface
Here’s a first cut at the code for a test that demonstrates JMockIt has simply created test doubles successfully: LoginServiceShould
To get this to compile and run, you’ll need the following additional source files: Account
AccountRepository
LoginService
Run your test and you’ll get a passing test if you have the classpath set correctly.
How?
If you open up the jmockit.jar, you’ll find a the manifest file in its standard location:
There’s a key line in the manifest:
This line registers the class as a Java Agent. This means it will have access to classes as their bytecode is loaded. JMockIt looks at the class structure to see if there are annotations it cares about. If there are, then it processes that class and gives an updated version of the class back to the class loader.
First Domain Test
Now that you have the project setup and running, it’s time to replace thefooShouldNotBeNull test with one that is relevant to our problem: LoginServiceShould
Lines | Description |
11, 14 | This annototation causes JMockIt to create mock objects for the next attribute. JMockIt creates a class that implements the interface named in the type of the attribute. It then inserts code into the constructor of the class that causes the attribute to be initialized with an instance of this JMockIt-generated class. |
19-22 | For each test method, create a fresh instance of a LoginService and provide an account repository. Since LoginSerivce is being provided a dependent object, this is called dependency injection. Since LoginService depends on an abstraction (an interface) rather than a concrete class, this is also applies the dependency inversion principle. Dependency injeciton and dependency inversion often happen at the same time. |
26-31 | Stub the method findAccountName such that when it is called it will return account for any string provided to the method. Since this is the same account repository used by the login service, any time the login service looks up an account (for this test), with any string, it will get accout. JMockIt uses anonymous inner classes to set up expectations. The trick it uses is an instance initializer. The second (inner-most) set of {} will be run at object creation time just before any constructor that exists. |
33-38 | Stub the method passwordMatches on the account object. No matter what you ask the account, it will say “yes this is my password”. This and lines 26 - 31 could be combined into one NonStringExpectations, however they are for different objects so I choose to not introduce unnecessary temporaily copuling between the lines. |
40 | Exercise the code under test. |
42 - 47 | Check, did what I want to have happen actually happen? Verify that the method incrementLoginCount() was called one time. |
Observations
The use of anonymous inner classes with instance initializers is not a new idea. It does lead to somewhat longer code for setup. I worry that as a result, the tendency will be for peopel to mix conceptually unrelated setup in the same block to avoid longer code. Looking at this and consdiering the equivalent in Mockito, I prefer the more terse form of Mockit, but this is perfectly fine. JMockIt has several features that Mockito does not, so this additial verbosity is probably going to be a worthwhile cost for additional power, but I’m not sure about this just yet.
Finally, I’ve recently come to the conslution thatno mock-library code should appear in the mainline code of a test. To understand/read/consume the test, I want it to be intention-revealing. JMockIT can certainly be read, but I don’t want to have to read it to understand the point of the test. The same can be said of all the other mocking libraries. So here’s a better example of how I want the tests to look:
Here are those private methods (extracted from the original test):
Verify Account Revoked After Three Failed Attempts
If you run this without changing the implementation of LoginService, you’ll see the following failure reported by JUnit:
This is an easy to read stack trace, the message indicates what we need to have happen. To make that happen, here’s one way to change the underlying production code:
Of course, I needed to add a field called failedCount, which is initialized to 0.
Comments