Introduction
Although using container in unit test is categorized as an anti-pattern (http://picocontainer.codehaus.org/tests-use-container-antipattern.html), it is interesting to use pico to bootstrap your test.
The anti-pattern seems to blame when you use a real container with real dependencies for a unit tests. But I think it's acceptable when
- You use a specific container containing the component you want to test all its dependencies are mocked.
- You are writing integration and functional tests
Using a container provides multiple advantages :
- Better flexibility in the component construction. Your tests won't break anymore when you change the constructor signature.
- Very little overhead. PicoContainer is very fast to start.
- Re-usability of container. You will probably create several test container depending on the system you want to test. Those container can be reuse in different test classes or even in different container.
Known disadvantages :
- More configuration in the test. You have to create, configure the container and get all desired instances.
- A bit excessive for simple unit tests. Such system is used to simplify complex construction of component. If the component require less than 2 dependencies, it doesn't worth it.
To lighten the container management in the test, a junit custom runner has been created which initialize the container and inject dependencies in the test class.
Usage of PicoRunner
PicoRunner is a simple junit custom runner which allows nice integration of picoContainer in Junit.
To get the full code of PicoRunner with examples, you can check IzPack sources.
Principle
Basically, in your test class, you define a container (implementing BindeableContainer) to use with the @Container annotation.
When Junit want to create the test class instance for each methods, the container is created and use to instanciate this test instance. Thus, your test class can behave like a component of the container and ask any dependencies it needs.
@Override protected Object createTest() throws Exception { Class<? extends BindeableContainer> containerClass = getTestClass().getJavaClass().getAnnotation(Container.class).value(); BindeableContainer installerContainer = getContainerInstance(containerClass); installerContainer.initBindings(); installerContainer.addComponent(klass); Object component = installerContainer.getComponent(klass); return component; }
Examples
Simple test
The first step is to create your container for test purpose.
public class StupidTestContainer extends AbstractContainer { /** * Init component bindings */ public void fillContainer(MutablePicoContainer pico) { pico.addComponent( System.getProperties() ) .addComponent(Mockito.mock(List.class) ) } }
Here we have 2 components, a Properties and a mock list.
To use this container in your test, use the customRunner PicoRunner and configure the container with @Container.
You will be able to ask any components of the container in the test constructor. Moreover, an instance of the test is created for each test methods. So you don't have to worry about cleaning the container.
@RunWith(PicoRunner.class) @Container(StupidTestContainer.class) public class StupidTest { private List mockedList; public StupidTest(Properties property, List list) { this.mockedList = list; } @Test public void testList() throws Exception { //using mock list mockedList.add("one"); mockedList.clear(); //verification verify(mockedList).add("one"); verify(mockedList).clear(); } }
Panel display tests
The following examples are taken from the izpack-test-panel module which contains small and fast GUI tests
@RunWith(PicoRunner.class) @Container(TestPanelContainer.class) public class TestPanelDisplay { private GUIInstallData guiInstallData; private FrameFixture frameFixture; private ResourceManager resourceManager; private InstallerFrame installerFrame; private UninstallDataWriter uninstallDataWriter; public TestPanelDisplay(GUIInstallData guiInstallData, InstallerFrame installerFrame, ResourceManager resourceManager, FrameFixture frameFixture, BindeableContainer container, UninstallDataWriter uninstallDataWriter) { this.guiInstallData = guiInstallData; this.installerFrame = installerFrame; this.resourceManager = resourceManager; this.frameFixture = frameFixture; this.uninstallDataWriter = uninstallDataWriter; }
The majority of instantiation logic is in the container. We have ready to use components and mocks for your test.
@Test public void htmlInfoPanelShouldDisplayText() throws Exception { addPanelAndShow("com.izforge.izpack.panels.htmlinfo.HTMLInfoPanel"); String textArea = frameFixture.textBox(GuiId.HTML_INFO_PANEL_TEXT.id).text(); assertThat(textArea, StringContains.containsString("This is a test")); } @Test public void licencePanelShouldDisplayText() throws Exception { addPanelAndShow("com.izforge.izpack.panels.licence.LicencePanel"); String textArea = frameFixture.textBox(GuiId.LICENCE_TEXT_AREA.id).text(); assertThat(textArea, StringContains.containsString("This is a licenSe panel")); }