Anyway, I want to share today the small library I've created to ease the life of my co-workers when faced with the tedious task of writing unit tests.
I won't do that again and again (and again)
I love writing unit tests. Ah, the smell of the CPU burning cycles to pass all those little creatures to green! Yet, starting from a blank page can be really tedious.
If you're like me, working with some kind of major Client-Server system, with tons of business objects and a fair deal of interfaces, you may find that just starting writing the first line of test code is no piece of cake. You're likely to need:
- some kind of "reference" data in your system, like a list of customers or a list of products
- some live-like data, like current prices
- some elaborate building of your "object under test", like a ConfirmationProcessor for orders confirmation
- some infrastructure to make sure that you don't need to go to a real database (think "mocks" here)
- some infrastructure to make sure that you don't need to access external webservices (think "mock" one more time)
Phew! I can pretty much guarantee that if you've gone through the trouble of coding all this machinery once, for your first test class, you will desperately want to reuse it a for your next test class!
Injection is for everyone: interfaces, objects, mocks and, yes, tests
If I rephrase the requirements above a bit differently, what I really, really, want is the ability to create Test Components where:
- one Test Component represents my Server, with all interfaces being mocks (those are RMI interfaces in my case)
- one Test Component provides a set of factories to create business objects easily and make as if they were saved on the Server (this uses the previous Test Component, right?). Once initialized, this component maken sure that a mininum of default "reference" objects are already saved (a customer and a product for example)
- one Test Component named CustomerOrder represents the "typical" customer order, with all necessary customer data, product selection,... This component also provides easy methods to change the product or the payment details, possibly using a DSL to do that concisely
- one Test Component is for the ConfirmationProcessor, what you really want to test, an object which creates and sends confirmations to the customer for their orders. This component, for example, is using a mock for the DiscountCalculator because it is not relevant in the context of sending confirmations
It must be clear, from the description above, that those Test Components may have lots of dependencies between them. Instantiating all those components properly can rapidly become a nightmare, but it happens that we know exactly how to deal with this nowadays.
Different shapes and flavors are available for Dependency Injection and I went with my favorite: Google Guice. Guice is going to "inject" all the required objects for my test:
- a mock Server
- a set of factories
- a typical customer order
- the context of my test
Show me the code
First of all, I need to show what I mean by TestComponent:
Armed with this, I can create my first concrete Test Components, leaving out the
/** something with a Guice module */
public interface HasGuiceModule {
GuiceModule module();
}
/**
* A simple test component using a Guice Module to inject its members
* when invoked by JUnit through the @Before annotation.
*
* If necessary, after the members injection, the subclass can use the localSetup method
* to add more initialization (like creating an initial customer) or more
* expectations (for mock objects)
*/
public abstract class TestComponent implements HasGuiceModule {
@Before
public void setup() {
inject();
localSetup();
expectations();
}
private void inject() {
module().inject(this);
}
protected void localSetup() {}
protected void expectations() {}
}
The component above is really JUnit-oriented but you could as easily add a main method which would call the setup.
module()
method definition for now:public class MockServer extends TestComponent {The code above shows a classic JUnit4 class, with one method annotated with
@Inject
Connection connection;
@Inject
CustomerInterface customers;
@Inject
ProductInterface products;
@Inject
OrderInterface orders;
// do all the wiring when the members have been injected with Guice
@Override
protected void expectations() {
when(connection.getCustomersInterface()).thenReturn(customers);
when(connection.getProductsInterface()).thenReturn(products);
when(connection.getOrdersInterface()).thenReturn(orders);
}
protected GuiceModule module() { /** to be defined later */ }
}
public class Factories extends TestComponent {
// this server interface provides methods to individual objects: a customer
// a payment method, a customer address,...
@Inject CustomerInterface customers;
// this factory provide easy to use methods to create
// fully setup customers with delivery address, payment method,...
// it uses the CustomerInterface interface to do so
@Inject CustomerFactory customerFactory;
protected GuiceModule module() { /** to be defined later */ }
}
public class ConfirmationProcessorTest extends TestComponent {
// the class under test
@Inject ConfirmationProcessor processor;
// the standard order, it uses the OrderFactory and CustomerFactory to save and
// update the customer and order
@Inject CustomerOrder customerOrder;
@Test public void aConfirmationForAGoldCustomerMustDisplayStars() {
// a DSL for describing customer types
customerOrder.setCustomerType("Gold 5Y 4*");
confirmationMustContain("*******");
}
private void confirmationMustContain(String content) {
assertTrue(processor.
confirmationFor(customerOrder.customer()).contains(content));
}
protected GuiceModule module() { /** to be defined later */ }
}
@Test
. The nice things to notice are:- the test data is injected in the form of a Test Component with a ready-to-use initial state
- that Test Component provides convenient ways to refine the initial state with data relevant to the test objective
So what's Guicy here?
So far, so good, but you must definitely feel that I left out part of the actual magic here. What about this
module()
method? How are GuiceModule
s defined? And why a GuiceModule
and not a Guice,...,Module
(i.e. com.google.inject.Module
)? The
module()
method is very straightforward. It just returns a GuiceModule which describes the bindings for the TestComponent:
public ConfirmationProcessor extends TestComponent {
protected void module() {
return new ConfirmationProcessorModule();
}
}
// and
public ConfirmationProcessorModule extends GuiceModule {
// bindings go here
}
So, what is a
GuiceModule?
Actually, a GuiceModule
is mostly a regular com.google.inject.AbstractModule
. It allows you to describe the bindings for a specific TestComponent. However, it is refinable, test-friendly and composable with other modules. Let's see how it works with several examples:
- A simple module
public MockServerModule extends GuiceModule {
@Override protected void configure() {
// equivalent to bind(Customers.class).toInstance(mock(Customers.class))
// this binding declares that in every TestComponent using this GuiceModule
// the Customers interface will be a mock
mockAndBind(CustomerInterface.class);
}
} - A module dependent on another
public MockFactoriesModule extends GuiceModule {
@Override protected void configure() {
// in these bindings, we declare that the factories are
// going to use an in-memory representation of the database
// the job of a Mock database is to use the mock server interfaces
// to make as if a saved object was always returned when required
bind(CustomersDatabase.class).to(MockCustomersDatabase.class);
}
@Override protected Set<GuiceModule> modules() {
// add a MockServerModule to the list of dependent modules
return module(MockServerModule.class);
}
} - A module with dependencies and local mocks
public ConfirmationProcessorModule extends GuiceModule {
@Override protected List<Class<?>> mocks() {
return classes(DiscountCalculator.class);
}
@Override protected Set<GuiceModule> modules() {
return modules(CustomerOrderModule.class);
}
} - A module refining another one and composed with other modules for a complex setup
new ConfirmationModule() {
@Override protected List<Class<?>> spies() {
return classes(ConfirmationProcessor.class);
}
}.remove(MockPrinterModule.class).add(RealPrinterModule.class)
As you can see on those examples, there is a lot of flexibility to allow you to reuse your existing modules as much as possible:
- by refining them with additional mocks / spies (subclassing the mocks method)
- by adding another module to form a larger one
- by removing / replacing another module
One question however remains unanswered. The dependencies among modules could be rather hairy and knowing that you can't declare bindings more than once with Guice, how do you manage to avoid conflicts? You can't afford to add modules referencing one another and face the nightmare of digging out why a given module has been referenced twice.
Transitive dependencies and Set of GuiceModules
The answer to this issue is simple. It is coded in the GuiceModule#getModules() method:
public Set<GuiceModule> getModules() {
Set<GuiceModule> result= new HashSet<GuiceModule>();
result.addAll(dependentModules); // a private list of added modules
result.addAll(modules()); // the ones declared by the subclass
for(GuiceModule m: modules()) {
result.addAll(m.getModules());
}
for(GuiceModule m: modulesToRemove()) {
result.removeAll(m.getModules());
}
result.add(this);
return result;
}
The
GuiceModule#getModules()
method adds or removes modules following all dependencies transitively (and recursively). The design is also kept voluntarily simple here. A Set makes sure that 2 "same" modules are never returned based on them being equal. And 2 modules are considered to be equal if they have the same class. This forbids the possibility of parameterizing modules but I haven't found the need yet to do that. My guess is also that evolving the design to accommodate that situation shouldn't be too difficult (add a smarter equal method).
Now my real question to the knowledgeable people around here is: is there a better way to do so with Guice?
I've found a way to override bindings from a module with bindings from another module. I've seen that you can create a hierarchy of injectors. I've seen that you can use scopes to specify the applicability of your bindings. But I haven't found anything like a simple "algebra" for modules so that they can be added, subtracted (or unioned / intersected if you prefer). Maybe this post title is provocative enough that I can get an enlightened answer!
Anyway, the supporting code I'm presenting here is just a few lines. All the heavylifting is done in Guice for dependencies resolution as well as in the Factories I've created to mock out the Server and create smart test data.
Last.tips.com
A Test Component is declaring lots of bindings but and in order to get the corresponding members injected, you either need to:
- inject the component itself to your test
- inherit from it
public class MyTestWithAnOrder extends TestComponent {You can also write:
// injecting the CustomerOrder builds an order which itself is injected
// forgetting that line would end up with an "empty" order
// (order.equals(new Order()))
@Inject CustomerOrder customerOrder
@Inject Order Order
public GuiceModule module() { return new CustomerOrderModule(); }
}
public class MyTestWithAnOrder extends CustomerOrder {But here's a trick. If you declare the following binding in the CustomerOrderModule:
// the order member can be inherited and properly setup by the superclass
public GuiceModule module() { return new CustomerOrderModule(); }
}
public class CustomerOrderModule extends GuiceModule {Then, you can just access the
@Override protected void configure() {
...
bind(Order.class).toInstance(new Order());
bind(CustomerOrder.class).toInstance(new CustomerOrder);
...
}
}
// given that CustomerOrder looks like
public class CustomerOrder extends TestComponent {
@Inject Customer customer;
@Inject Order order;
@Inject OrderFactory orderFactory;
// this is where, as a post-construct step, the order object gets fully setup
@Inject
protected void localInit() {
orderFactory.saveAsStandardOrder(order);
}
public void GuiceModule module() { return new CustomerOrderModule(); }
}
order
dependency in your test without having to reference the CustomerOrder
TestComponent: public class MyTestWithAnOrder extends TestComponent {
// it just works (tm)
@Inject Order Order
public GuiceModule module() { return new CustomerOrderModule(); }
}
The other tip I want to share is the addition on the Factories objects of
autoGet()
methods. Let's say you need, for your test, to make as if a customer existed in the database. The usual thing to do is to use the test factory to create a new
Customer
object and use mocks to have it returned when someone calls getCustomer(id)
. But you may actually not really care about which customer it really is. So why not go one step further with an autoGet()
method? Calling
autoGet()
on the factory will just set-up the mock object representing the CustomerInterface
on the Server so that every time a customer is requested with an id, you simply create one on the fly and return it! (see here to see how to do it with Mockito).Conclusion
I think the take-away points from this post are:
- Test components are worth investing in
- But then they have to be highly reusable
- Guice is a great tool to manage dependencies
- Dependencies are better off with some operations to combine them
7 comments:
Your posts are as rare as they are interesting. Thanks for this one, raises a lot of ideas... One of them stem from my musings with Haskell: TestComponents are a form of Monad, the monad of environmnets for test.
Regards,
Arnaud
Thanks for the comment. I'd be very interested if you can post a follow-up to show how TestComponents can be a form of Monad (what's the effect of the bind operation?)! As far as I'm concerned I may add a small post showing how it is very easy (and helpful) to plug Fitnesse on top of this.
Thanks for the challenge ;-)
I shall give it a try hopefully this week. And I am much interested in your fitnesse stuff too!
BTW, will you be in Scala Days 2010 ?
Unfortunately, I'm based in Sydney and the ScalaDays are going to be a bit to far for my budget :-(.
I'll post the Fitnesse stuff as soon as I have a bit more time to think about it again. I'm not very satisfied with what I've done so far. It works but I've used statics where Singleton injection should do the trick. Hopefully that'll be clearer with one or 2 examples,...
Quick comment about the mockAndBind function. I've found that mocking a class also requiring DI causes those dependencies to be required in the module. I've find the simplest solution is to declare the mocked class via a Provider instead, e.g. mockAndBind becomes:
protected <T> void mockAndBind(final Class<T> mockClass) {
bind(mockClass).toProvider(new Provider<T>() {
public T get() {
return mock(mockClass);
}
}).asEagerSingleton();
}
Very Interesting, I didn't realize that. Thanks for the comment Damien, I'll update my own code accordingly!
Damien, we had the issue you mentioned today at work (I was waiting for that to change my code,...). Your fix worked perfectly. Thanks a lot!
Post a Comment