Saturday, December 29, 2007

Packaging a JMock Plugin for Eclipse

I have not seen a standard JMock plug-in for Eclipse (I did not search too closely). In any case, this entry will provide a demonstration for one way to use 3rd-party libraries in your own Eclipse project. Keep in mind that the licensing and distribution of 3rd-party libraries is a different matter entirely (but if anyone out there has any information on it, I would certainly be interested in learning).

Download the JMock library


The latest stable release of JMock is 2.4.0 and can be downloaded here, or in a unix shell with
  • wget http://www.jmock.org/dist/jmock-2.4.0-jars.zip
We will also need to unzip jmock-2.4.0-jars.zip to access the jars.
I am not particularly fond of the way the source and binaries are packed together in some of these libraries. My preference would be to isolate the functional code from the IDE resources (IDE usage appears to be the motivation for the current packaging scheme).

I also like to have the JMock javadocs on hand and we can download them here or in a shell
  • wget http://www.jmock.org/dist/jmock-2.4.0-javadoc.zip

Decide How to Package the Library


I have seen various ways to package 3rd-party libraries in Eclipse plug-ins, and the final word on packaging a library generally comes from the owner. Until jmock.org provides an Eclipse distribution, whatever method we choose will be fine, but it is in no way the authoritative means of distribution.


Embedded as a Jar in a plug-in with custom code

3rd-party libraries in the same plug-in as custom code is not good form.


While this might be fine for very small projects, I would caution against it. There is always a chance that the library owner will create a standard Eclipse plug-in someday, or that someone else will produce an Eclipse plug-in for a functionally equivalent (or better) library, so it is a good idea to keep our code separate from their code.




Unwrapped binary or source plug-in

Unpacked 3rd-party libraries in a separate plug-in.


Unless we were planning to modify code in the 3rd-party library (in which case we would be forking the distribution and assuming responsibility for the modified code) there is really no need to unpack the archive. Why should our automated build process spend cycles re-compiling unchanging library source code or re-packing standard binaries? I am confident that library jars inside plug-ins incur little to no performance penalty over plug-in jars with unwrapped binaries (unfortunately, I do not recall where I originally read that and would like to provide a cited reference).




Plug-in containing multiple jars

Multiple packed 3rd-party libraries in a separate plug-in.


This will probably satisfy our requirements. Since the JMock libraries and their dependencies are distributed as a single unit, I feel comfortable putting them all inside a single plug-in and exposing only the top-level org.jmock packages (along with org.hamcrest packages as needed). The PDE provides a good way to manage dependencies among version-ranges of libraries. Because JMock is already provided as a distribution with a single, downloadable file that contains specifically-versioned dependencies, that management is already handled for us (it just happens that the version range is restricted to a single value per library).




Plug-in per jar

One 3rd-party library per plug-in.


This would be ideal, but, as consumers of the libraries and not as the actual distributors, it should not be up to us to manage declaration of the interdependencies of the libraries and the fine-grained packaging and versioning of these plug-ins for Eclipse. Individual plug-ins probably would not benefit us much over the multi-jar plug-in approach, especially if we plan to upgrade the JMock distribution as new versions become available, and this approach may even add unwanted maintainance costs for re-packaging the libraries into 8 plug-ins, as opposed to 1 plug-in, every few months.




Create a new Eclipse Plug-in Project


Now that we have decided to package the JMock distribution as a single plug-in, the creation of the plug-in project itself is very straightforward with the New Project Wizard.

The New Project Wizard handles most of what we need.

New -> Project... -> Plug-in Development -> Plug-in From existing JAR archives

Add the JARs that came with the JMock download.

Select all the JMock JARs that were unzipped from the downloaded archive. If you had originally put these JARs inside a plug-in with your test code and are now moving them into their own plug-in, then you can Add... the JARs from your Workspace. Since we have the JARs stored in some other location on the filesystem, we will Add External....

Choose a name and version for the plug-in and don't forget to un-check the Unzip option!

The final word on the naming and versioning of a plug-in, much as the physical packaging, comes from the owner of the library. If we were planning to modify the JMock source code, then we would probably use the same naming convention that we use for our other plug-in projects, for example we would call our fork of JMock my.company.jmock. Since we are planning to consume JMock as-is, then we will want to give the plug-in a more conventional name, such as org.jmock. Similarly, since we will not modify the libraries, we can follow the current jmock versioning scheme and say that the plug-in has a version of 2.4.0.
By default, the Wizard checks the Unzip the JAR archives into the project option; here we want to uncheck that option.


I prefer to restrict the packages that are exported just to what I will access.
By default, the Wizard will automatically export all available Java packages for the plug-in. My preference is to export only those packages that I will access from my test code. To get started, the list includes org.jmock, org.jmock.api and org.jmock.integration.junit4. As we write more tests, we might need some custom matchers that are available in org.hamcrest.Matchers but are not exposed by JMock.

Get JUnit 4.4


Unfortunately, just as with Spring 2.5 JUnit support, JMock has a dependency on JUnit 4.4, but JUnit 4.3 is bundled with Eclipse. If we were to try to use the JMock.class runner in a JUnit 4 test case, we would see the following compilation error:
  • The type org.junit.internal.runners.JUnit4ClassRunner cannot be resolved. It is indirectly referenced from required .class files
Compilation exception because JMock requires JUnit 4.4.

Since JMock depends on JUnit 4.4, and since we are bundling JMock and its dependencies as a single plug-in, should the JUnit 4.4 jar be included in the same plug-in even though it is not part of the downloadable distribution on jmock.org?  I do not know an absolute right answer.
We can get the JUnit 4.4 jar from here or download it from a unix shell with
  • wget http://downloads.sourceforge.net/junit/junit-4.4.jar?modtime=1184865382&big_mirror=0
We can import the junit-4.4.jar into our org.jmock plug-in with the Import... -> File System Wizard.

Once the junit4.4.jar is in the workspace, we can add it to the plug-in's classpath in the MANIFEST.MF.

Add Junit4.4 to the plug-in's classpath.

Write a JMock Test Case


The JMock Cookbook is a great resource for getting started with JMock. Our goal is to get a single test-case running with a mock object to verify our plug-in configuration.
We can declare our dependency on org.jmock in our test plug-in's MANIFEST.MF. We want to ensure that org.jmock is declared as a dependency before org.junit4.
Declare org.jmock before org.junit4 dependency.


I have seen the following initialization error from the JMock test runner if org.junit4 is declared first (presumably because JMock is using JUnit 4.4 and Eclipse is using 4.3)
Failure trace when org.junit4 dependency is declared before org.jmock dependency.

We can create a new test case that uses a Mock Object and the JMock test runner. The following code should be enough:

package my.test.plugin;

import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JMock;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(JMock.class)
public class SomeTest {
private final Mockery mockery = new Mockery();

@Test
public void testSomething() throws Exception {
final Callable<?> callable = mockery.mock(Callable.class);
Executors.newSingleThreadExecutor().submit(callable).get();
}
}

We should now see a legitimate test failure! We expect that the Executor will call Callable#get, and from the JMock failure trace, that is exactly what happened.

We are expecting our test case to fail.

Now let's emend our test code so that the mock object expects an invokation of Callable#call.

package my.test.plugin;

import org.jmock.Expectations;
....

@Test
public void testSomething() throws Exception {
final Callable<?> callable = mockery.mock(Callable.class);
mockery.checking(new Expectations() {
{
one(callable).call();
}
});
Executors.newSingleThreadExecutor().submit(callable).get();
}
}


We have a green bar!
We are expecting our test case to fail.

It appears that we have successfully incorporated the JMock 3rd-party libraries into a re-usable Eclipse plug-in.

2 comments:

Nat Pryce said...

Hi. I have a couple of questions...

What does your jMock eclipse plugin do?

Why use a plugin instead of putting the JAR into a lib/ directory in your project?

Tim Myer said...

Great questions:
I currently use this jMock plug-in as a dependency of my own Eclipse plug-in test fragments. Whenever I write a new Eclipse plug-in, say tims.eclipse.plugin, I create a parallel test fragment (in this case tims.eclipse.plugin.tests). The fragment adds an additional dependency on the org.jmock plug-in in order to use the classes in any package exported by the jMock plug-in.
Centralizing the jMock plug-in in a single location allows all of my test fragments to reference that single plug-in, instead of each fragment embedding the jar in its own lib directory. Also, if jMock eventually releases an official plug-in, then I will be able to replace mine with the official version with very little pain. In addition, as the jMock library changes over time, using a plug-in will make upgrading and version-management much easier than having jars spread across test fragments. For these types of reasons, it is generally bad form to embed a third-party jar directly in a plug-in.