Wednesday, September 7, 2011

Obstacles to Learning JMockit

JMockit employs some very strange tricks to keep your mock object setup short and declarative. The following list of strangeness may inoculate you against complete bafflement when you try to learn from the tutorials:

First, an overview of basic terminology and concepts so you can understand the weirdness:

  • JMockit uses a record-replay-verify model in which mock object behavior is "recorded" in an "expectations block", then test code is run (replaying the expected behavior), then mock object interactions are verified in a "verifications block".
  • JMockit is primarily used to mock object types (Interface or Class), not object instances. In the example below, any invocation of the method "mockMethod()" on an instance of "Dependency" will return the mock result "someValueOrException" regardless of the instance invoked! This is handy if your "objectUnderTest" does not provide a way to set a mock object instance.
  • Any type (not instance) of a field/parameter declaration preceded by the @Mocked annotation (or similar annotation such as @NonStrict) or declared as a field in an expectations block will be mocked by JMockit.
  • An expectations block is used to declare the method calls you expect your unit test may invoke on mock objects and the return values (or exceptions thrown) that you want your unit test to get back from those invocations.
  • A verifications block is used to declare the method calls your unit test must (or must not) invoke.
   @Test
public void testOperationXyz()
{
...

new NonStrictExpectations() // an "expectation block"
{
Dependency mockInstance; // "Dependency" is our mocked type for this test
...

{
...
// "mockInstance" is a mocked instance automatically provided for use in the test
mockInstance.mockedMethod(...);
result = someValueOrException; // declare expected result of first invocation
...
}
};

...
MyUnitClass objectUnderTest = new MyUnitClass();
objectUnderTest.operationXyz(); // test invocation
...

new VerificationsInOrder() {{ // a "verification block"
...
mockInstance.mockedMethod(...);
times = 1; // declare some property of this method invocation to be verified
...
}};

// Additional verification code, if any, either here or before the verification block.
}

Now, here are the aspects of JMockit that I found very surprising:

    • The declaration of mock behavior is specified in an anonymous class constructor (new NonStrictExpectations() {...} for example), but the result of this constructor is not saved explicitly. The association of this behavior with the actual invocations of mock object methods at runtime occurs as a side effect!
    • The mock behavior is declared in an "instance initializer" block within the anonymous class constructor, but since no instance is created in your test code, the state being initialized is not really an instance in the normal sense.
    • The mock behavior declaration commonly consists of an invocation of a mocked object method followed by a "result = ;" statement. This is intended to be read as "recording" that when the mocked object method is invoked as indicated, the result returned should be the value of . HOWEVER, this is an overlaying of a new language semantics upon Java code. The actual Java semantics dictate that during construction of the anonymous class instance, the mocked object is invoked and then expression is assigned to the Expectation field "result". Since Java semantics dictate there is no shared state between the invocation and the assignment to "result", these statements could appear in any order with any other statements between, but JMockit "recording" semantics require that the assignment to "result" appear right after the invocation. A sequence of multiple "result = ..." statements after an invocation records the values to return (or exceptions to throw) for each of a sequence of matching invocations. How the execution of the anonymous class constructor achieves the intended semantic side effect of defining subsequent mock object behavior is a mystery. The key seems to be that JMockit modifies the class to be mocked at runtime by reflecting on the behavior declared in this instance initializer block. Note that the first statement in "result = x; result = y;" would be useless in any Java program normally, but JMockit reflects on this code to record a sequence of results to be returned on later invocations.
    • JMockit allows you to specify a mocked method invocation return value AND a thrown exception with the same "result = " syntax. If you need to specify that a Throwable is returned and not thrown, you have to use the alternate "returns()" method.
    • You may use annotations to declare mock objects, or not. When the object is declared in an expectation block, its type is mocked automatically, so the annotation is optional.
    • Mocked objects are implicitly instantiated; you don't need code to instantiate them. By default, even if you instantiate and initialize an instance of a mocked class outside of the JMockit specific code, you will get the mocked behavior, because JMockit has modified the bytecode of the class itself.
    • "JMockit does not care on which object a mocked instance method is called." The mocked behavior is shared by all mocked instances of a type so that JMockit can inject this mock behavior everywhere the type is used, even when the instance is constructed within the unit under test. If you want per-instance mocking, @Injectable gives us an "exclusive" mocked instance; other instances of the same type are not mocked.
    • How do you mock a constructor invocation? Invoke the constructor in an expectations block, followed by exceptions thrown, if any.
    • How do you mock other static methods on a class? Invoke the static methods in an expectations block.
    • Requires Java 6 (or Java 5 with a vm argument). Unit tests we write will not run in a Java 5 build environment without the vm argument!!!
    • @Cascading indicates that expectations for a cascade of invocations (e.g., mock.getChannel().isConnected()) can specify the result of the cascade without having to specify the result of each separate invocation. This is a nice feature.
    • Debugging a JMockit unit test can be very difficult, because the internals of how mock object results are returned are not well documented. For example, if arguments to a mock object invocation do not properly implement "equals(Object other)" then your invocation may not match any expected invocation and will return null instead of your intended result. It is very difficult to step through the mock object framework matching code to find the particular argument that is failing to match.

    You might ask yourself if JMockit is worth learning and using. I certainly did and my conclusion was that you should definitely use a simpler framework if your unit under test allows you to create and set the mock objects you need. For example, if the objects to be mocked all can be injected with setter methods and if all implement an Interface type that you can implement with a mock implementation, then you do not need any mock object framework. If you need the ability to mock static methods and inject mocks into objects under test that do not have setter methods, then JMockit is probably the simplest mock framework to use after you master its unusual API.

    The main alternative that I've heard of is PowerMock in combination with the EasyMock framework. This alternative offers essentially the same power with a more verbose, but natural syntax. However, I don't know that learning PowerMock/EasyMock is any easier for the cases where such power is needed, and the resulting code is probably more difficult to maintain, if only because it is more verbose.

    Sunday, December 28, 2008

    This my first time, writing a blog entry for friends and family. I'd like to share some of the daily goings on here, and so, perhaps, avoid the trouble and annoyance of sending a holiday letter. But, since I haven't been blogging, here is the 2008 holiday letter.

    We are well and happy this holiday season. This is remarkable given that just two years ago I'd been under-employed for the previous two years, out of cash, and we'd just gambled our home in Portland for a rising real-estate market and steady work in Seattle. As we all know, the real-estate market dove and the economy went bust shortly afterword, but my job is still secure and we are able to pay our bills. In the big picture, we are so much more optimistic about the next four years with new leadership in Washington that the burst of the real-estate bubble is something we are prepared to deal with.

    The year started with a bit of magic. The orcas that had entirely skipped south Puget Sound the previous winter, came for Christmas 2007 and stayed for New Years. We got to see them at a distance from our house and up close as they rounded the point by the lighthouse.

    As spring approached, Jan and Liz conspired to bring home 6 new pets. Chicks as it turned out. After the first month stinking up Liz' bedroom, we moved them to their own new chicken mansion on the hill and it wasn't long before they produced a first egg!









    Jan found good part-time work at a consignment shop. Not only could she work around Liz' school schedule, but she got to know most everybody who shops in town. Another benefit: she was called on to fill in as needed on a softball team.

    I didn't have much time for such sport; I was spending three hours a day commuting and nine at software development. Not that the commute was without its pleasures. Some of my favorite people can be found on the back of the bus each day.

    In fact, commuter friends have invited me and my family to some fine parties, scotch, and sailing. Laura took us sailing on progressively more ambitious trips; first around the harbour, then camping, then just just a few weeks ago I sailed with her in a 50 mile race. That was the night that the first big snow hit. I'll never forget sailing back in the dark, watching the lights of Tacoma behind us extinguished one-by-one as an inky black storm cloud swept toward us.

    But in the summer, sailing was warm and glorious. Everything about this summer was glorious. Let's forget about the rains and just remember some of the great sunrises.









    Some of the highlights included Liz tubing behind Joel's waterski boat, the chickens growing up, and my attempt to climb Mt. Index with Bob.









    When we did take vacation, it was even better. Sequoia joined us for a backpack trip that took us to one of the greatest swimming holes ever. Well, it would've helped if the water was warm enough to swim in for more than just a few seconds and the horse flies were a bit of a problem. :-/ We also went to Mt Baker and it turned out that our dog Emily is the greatest hiking companion ever. Not only is she well behaved on the trail, but it turns out that she has uncommonly good sense about when its not safe to cross a raging glacial stream. Perhaps she saved me from serious injury on this particular hike.









    After summer, it was back to the grindstone for me. My major recreation was going to Liz' soccer games on Saturdays. Her team was amazing. With good coaching, the girls began to play together not just well, but with inspiration! There was a lot of talent and they learned to pass effectively and to set up some amazing plays. They had great fun with the games and I was told they ended with the best record in their league. What else notable happened this fall? Oh yeah, we all did everything we could to see Barak Obama get elected. Still, none of us was quite prepared for the election result when it was announced. We were at a big election night party and tears were flowing as our President-Elect gave his acceptance speech. The picture of Evy with balloons is from Mom's birthday party back in August, but let's pretend she was celebrating the election result too.










    There's really nothing else to report that tops that, but our big pre-Christmas snow storm was a great way to end the year. I've never seen so much snow at home in the Northwest, but what was most remarkable was that the snow was on the ground for a full two weeks. When there were just a few inches on the ground, I kept biking to the ferry. Even as bus service was disrupted and some co-workers were walking miles to get to work, Expeditors would not close the office. I was proud to be able to make it in, but it started to cost me extra as I had to pay for the passenger only ferry instead of using the bus and car ferry route that I'd already paid for.

    Finally, when there was a foot of unplowed snow on the road for two miles between me and the ferry, Expeditors closed the office. That day, we lost power and found the cause was two trees and a power pole across our road, a quarter mile from our house. We had a fine candlelight dinner with all our neighbors that night. The next day, our power was still out, but Expeditors was open. I crawled under the power pole, tiptoed around the dead power lines, and climbed over the trees to get out with my bike. The snow had turned to slush, but was still too slick to bike on. I had to walk up most hills and ride in the ruts to get to the highway. Once there, I still had to hop off and carry the bike off the road when cars passed, as the were no plowed shoulders. That night, I got a ride home in a pickup truck. The power was back on. Christmas eve, Expeditors was closed again, so I was able to enjoy a fine long holiday.

    Merry Christmas to all and Happy New Year!