Project

General

Profile

IntegrationTests » History » Version 9

Andreas Müller, 02/23/2022 09:37 PM

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