Project

General

Profile

IntegrationTests » History » Version 6

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

1 1 Ben Clark
{{>toc}}
2
3
4
-----
5
6
7
# Testing java code using Maven and Unitils
8
9
10 3 Andreas Kohlbecker
11
## Configuration files
12
13
14 4 Andreas Kohlbecker
There are two  [Unitils](http://www.unitils.org/summary.html)   configuration files in the cdmlib project which contain among other stuff the database connection and the like for  the Unitils _!DatabaseModule_ and _!DbUnitModule_:
15 3 Andreas Kohlbecker
16
* **cdmlib-io:** `/cdmlib-io/src/test/resources/unitils.properties` 
17
18
* **cdmlib-persistence:** `/cdmlib-persistence/src/test/resources/unitils.properties` 
19
20
 
21 5 Andreas Kohlbecker
HINTS: (see also #3293)
22
23
24 6 Andreas Kohlbecker
1. To access the H2 data base you have to start the server at some point in the code and you have to set a breakpoint of course because the data base only exists during the runtime of the test.
25 5 Andreas Kohlbecker
26
1.  You can have a look into the H2 data base by starting the H2 web server
27 1 Ben Clark
28 6 Andreas Kohlbecker
  <pre>
29
30 1 Ben Clark
org.h2.tools.Server.createWebServer(new String[]{}).start();
31 6 Andreas Kohlbecker
32
</pre>
33
34
  which is then available under localhost:8082. 
35 3 Andreas Kohlbecker
36
37
----
38
39
_Everything below this line may be **OUTDATED** and needs to be reviewed._
40
41
42
----
43
44
45 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:
46 1 Ben Clark
47
48
* Run fast (they have short setups, run times, and break downs). 
49
50
* Run in isolation (you should be able to reorder them). 
51
52
* Use data that makes them easy to read and to understand. 
53
54
* Use real data (e.g. copies of production data) when they need to. 
55
56
* Represent one step towards your overall goal. 
57
58
59
[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.
60
61
62 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:
63 1 Ben Clark
64
65
~~~
66
public class SimpleTest extends UnitilsJUnit4 {
67
  
68
    private ClassUnderTest classUnderTest;
69
    private String testData;
70
    
71
    @Before
72
    public void setup() {
73
        classUnderTest = new ClassUnderTest();
74
        testData = "Lorem";
75
    }
76
77
    @Test
78
    public void testMethod() {
79
        String result = classUnderTest.method(testData);
80
81
        assertEquals("Ipsum",result);
82
    }  
83
} 
84
85
~~~
86
87 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. 
88 1 Ben Clark
89
90
Unitils only really begins to show a benefit, however, when you want to do something more ambitious than a straightforward unit test.
91
92
93
94
## Testing persistence-related functionality
95
96
97 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.
98 1 Ben Clark
99
100
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.
101
102
103
~~~
104
@SpringApplicationContext("path/to/your/applicationContext.xml")
105
public class PersistenceTest extends UnitilsJUnit4 {
106
  
107
  @SpringBeanByType
108
    private DaoUnderTest daoUnderTest;
109
    private String testData;
110
    
111
    @Before
112
    public void setup() {
113
        testData = "Lorem";
114
    }
115
116
    @Test
117
    @DataSet("PersistenceTest.testMethod.xml")
118
    @ExpectedDataSet
119
    public void testMethod() {
120
        String result = daoUnderTest.method(testData);
121
122
        assertEquals("Ipsum",result);
123
    }  
124
}
125
126
~~~
127
128
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@. 
129
130
131
132
## Testing service and controller layer classes using mocks
133
134
135 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.
136 1 Ben Clark
137
~~~
138
public class ControllerTest extends UnitlsJUnit4 {
139
    @Mock
140
    @InjectInto(property = "service")
141
    private Service mockService
142
143
    @TestedObject
144
    private TestController controllerUnderTest;
145
146
    private String testData;
147
148
   @Before
149
    public void setup() {
150
        testData = "Lorem";
151
        mockService = EasyMock.createMock(Service.class);
152
        controllerUnderTest = new TestController();
153
    }
154
155
    @Test
156
    public void testMethod() {
157
        EasyMock.expect(mockService.serviceMethod(EasyMock.eq(testData))).andReturn("Ipsum");
158
        EasyMock.replay(mockService);
159
160
        String result = controllerUnderTest.method(testData);
161
162
        assertEquals("Ipsum",result);
163
        EasyMock.verify(mockService)
164
    }  
165
}
166
~~~
167
168 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.
169 1 Ben Clark
170
171
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.
172
173
174 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.