Project

General

Profile

IntegrationTests » History » Version 2

Andreas Kohlbecker, 02/05/2013 10:34 AM

1 1 Ben Clark
{{>toc}}
2
3
4
-----
5
6
7
# Testing java code using Maven and Unitils
8
9
10 2 Andreas Kohlbecker
If we're following good test driven design practices, then we should be writing tests before we write code and running them often to demonstrate that our code works. It is important that tests:
11 1 Ben Clark
12
13
* Run fast (they have short setups, run times, and break downs). 
14
15
* Run in isolation (you should be able to reorder them). 
16
17
* Use data that makes them easy to read and to understand. 
18
19
* Use real data (e.g. copies of production data) when they need to. 
20
21
* Represent one step towards your overall goal. 
22
23
24
[Maven](http://maven.apache.org) gets us some of the way towards this by providing a standard build cycle, integration with surefire, a testing harness that isolates the classes under test in their own boot loader to ensure that only dependencies specified are present in the test environment, dependency management, etc.
25
26
27 2 Andreas Kohlbecker
Nevertheless, it is still easy to write tests that don't follow the principles outlined above. One way to make your life easier is to use some libraries to provide boilerplate code (i.e. code which you're likely to use from one test to the next) to make it simpler and easier to do complicated things within your test. One really interesting tool is [Unitils](http://www.unitils.org) which is a meta-framework which ties several testing api's together. An example of a simple unit test using unitils is as follows:
28 1 Ben Clark
29
30
~~~
31
public class SimpleTest extends UnitilsJUnit4 {
32
  
33
    private ClassUnderTest classUnderTest;
34
    private String testData;
35
    
36
    @Before
37
    public void setup() {
38
        classUnderTest = new ClassUnderTest();
39
        testData = "Lorem";
40
    }
41
42
    @Test
43
    public void testMethod() {
44
        String result = classUnderTest.method(testData);
45
46
        assertEquals("Ipsum",result);
47
    }  
48
} 
49
50
~~~
51
52 2 Andreas Kohlbecker
There is one thing which is worth noticing about the test above – it sets up test data outside the test itself. There are two reasons for this. Firstly, its better to set up the objects needed and the data required outside of the test itself if you can. It makes the test easier to read, and by using @@Before@, which is run prior to every test, the test data is restored to the same state at the beginning of each test. This isn't always possible, but if you need to initialize many objects prior to a test, this might mean your test is too complicated and needs to be broken down further. 
53 1 Ben Clark
54
55
Unitils only really begins to show a benefit, however, when you want to do something more ambitious than a straightforward unit test.
56
57
58
59
## Testing persistence-related functionality
60
61
62 2 Andreas Kohlbecker
If we want to test the persistence layer in the cdm properly, we need spring (our DAO's are spring beans), hibernate (our favourite ORM tool), and a test database with data in it. We want to be able to insert data into the database before we run a test, and if we modify the state of the database during a test, we want to check its state afterwards. [DBUnit](http://dbunit.sourceforge.net/) is a tool to help us do this, and unitils makes it easy to write tests using dbunit, hibernate and spring.
63 1 Ben Clark
64
65
To run an integration test using hibernate, substitute the normal datasource (e.g. `org.springframework.jdbc.datasource.DriverManagerDataSource@) with an instance of @org.unitils.database.UnitilsDataSourceFactoryBean` in your spring application context. This bean picks up the configuration held in a file called unitils.properties (see source:"trunk/cdmlib/cdmlib-persistence/src/test/resources/unitils.properties") located somewhere in your classpath. To specify the application context to use in the test, use the annotation `@SpringApplicationContext` e.g.
66
67
68
~~~
69
@SpringApplicationContext("path/to/your/applicationContext.xml")
70
public class PersistenceTest extends UnitilsJUnit4 {
71
  
72
  @SpringBeanByType
73
    private DaoUnderTest daoUnderTest;
74
    private String testData;
75
    
76
    @Before
77
    public void setup() {
78
        testData = "Lorem";
79
    }
80
81
    @Test
82
    @DataSet("PersistenceTest.testMethod.xml")
83
    @ExpectedDataSet
84
    public void testMethod() {
85
        String result = daoUnderTest.method(testData);
86
87
        assertEquals("Ipsum",result);
88
    }  
89
}
90
91
~~~
92
93
Unitils will autowire properties annotated with `@SpringBeanByType` and `@SpringBeanByName` with beans from your application context. Annotating the class or individual test methods with `@DataSet("datasetname.xml")` results in unitils loading a dbunit dataset into the test database prior to the test starting up. If the test class is located in eu/etaxonomy/cdmlib/persistence/ then the data set needs to be located there too. `@ExpectedDataSet` checks the state of the database at the end of the test against a data set file located in package/ called `TestClassName.testMethodName-result.xml` . There is no such convention for @@DataSet@, but I follow the same convention any way, naming dataset input files @TestClassName.testMethod.xml@. 
94
95
96
97
## Testing service and controller layer classes using mocks
98
99
100 2 Andreas Kohlbecker
If you don't depend upon external code that is too complicated to replicate otherwise, you can avoid using spring and hibernate To fully isolate the class under test, you can inject mock objects into the dependencies of a class under test, to verify its behaviour without creating real objects which can act as a point of failure outside of the class under test. [EasyMock](http://www.easymock.org/) is a framework that provides mock objects, and provides tools for verifying their behaviour.
101 1 Ben Clark
102
~~~
103
public class ControllerTest extends UnitlsJUnit4 {
104
    @Mock
105
    @InjectInto(property = "service")
106
    private Service mockService
107
108
    @TestedObject
109
    private TestController controllerUnderTest;
110
111
    private String testData;
112
113
   @Before
114
    public void setup() {
115
        testData = "Lorem";
116
        mockService = EasyMock.createMock(Service.class);
117
        controllerUnderTest = new TestController();
118
    }
119
120
    @Test
121
    public void testMethod() {
122
        EasyMock.expect(mockService.serviceMethod(EasyMock.eq(testData))).andReturn("Ipsum");
123
        EasyMock.replay(mockService);
124
125
        String result = controllerUnderTest.method(testData);
126
127
        assertEquals("Ipsum",result);
128
        EasyMock.verify(mockService)
129
    }  
130
}
131
~~~
132
133 2 Andreas Kohlbecker
In the example above, there is an instance (declared using an interface) _mockService_ which we're not interested in testing and an instance _controllerUnderTest_ which we do want to unit test. The behaviour of `TestController` which we're testing depends upon an instance of `Service` being injected in to our controller. By annotating our mock with `@Mock` and `@InjectInto` and annotating our class under test with @@TestedObject@, unitils handles the dependency injection for us.
134 1 Ben Clark
135
136
We still need to create our mock, using the static method @EasyMock.createMock(Interface.class)@. We then tell easymock what behaviour we expect of our mocks (in this case, we expect the method _serviceMethod_ to be called, and we expect the argument to be equal to _testData_). We want our mock to return the value "Ipsum" when this method is called.
137
138
139 2 Andreas Kohlbecker
Once we've finished defining the expected behaviour of our mocks we call `EasyMock.replay(mock1, mock2, . . .)@. We then run our test. After the test has finished, we call @EasyMock.verify(mock 1, mock2, . . . )` to verify that the expected behaviour occurred.