Migrate old wiki component tests best practices
[docs.git] / docs / developer-guides / tests / component.rst
1 .. _component:
2
3 ############################
4 Component Tests (with Guice)
5 ############################
6
7 *Note: This document is a work in progress.*
8
9 Introduction
10 ============
11
12 Code which uses `dependency injection with standard
13 annotations <https://wiki-archive.opendaylight.org/view/BestPractices/DI_Guidelines>`__
14 is well suite for component tests.
15
16 Component tests cover the functionality of 1 single ODL bundle (e.g.
17 netvirt's aclservice, genius' interfacemanager, etc.) They are focused
18 on end-to-end testing of APIs, not individual internal implementation
19 classes (that's what Unit Tests are for). They focus on testing the code
20 in their own module, and typically stub required external services. They
21 assert outcome on the system, often those external services, and the
22 data store. They wire together the internal beans through a Dependency
23 Injection (DI) Framework called Guice, which leverages standard Java
24 annotations in the code, which is equally used by Blueprints.
25
26 This `presentation from the ODL Summit
27 2016 <https://docs.google.com/presentation/d/1bnwj8CrFGo5KekONYSeIHySdkoXZiewJxkHcZjXnzkQ/edit#slide=id.g17d8ae4d92_0_137>`__
28 has some content related to this topic.
29
30 Maven
31 =====
32
33 In order to use Google Guice in your end2end API component tests to wire
34 bean objects using the same annotations as BP, use:
35
36 .. code:: xml
37
38    <dependency>
39      <groupId>org.opendaylight.infrautils</groupId>
40      <artifactId>inject.guice.testutils</artifactId>
41      <version>${infrautils.version}</version>
42      <scope>test</scope>
43    </dependency>
44
45 Code
46 ====
47
48 Object Wiring Binding
49 ---------------------
50
51 You can write Guice object binding wiring classes like this:
52
53 .. code:: java
54
55    public class AclServiceModule extends AbstractGuiceJsr250Module {
56        @Override
57        protected void configureBindings() {
58            bind(AclServiceManager.class).to(AclServiceManagerImpl.class);
59            bind(AclInterfaceStateListener.class);
60        }
61        ...
62    }
63
64 for any OSGi service external to the bundle (not local bean) you use
65 bind() like this:
66
67 .. code:: text
68
69    bind(DataTreeEventCallbackRegistrar).annotatedWith(OsgiService.class).to(DataTreeEventCallbackRegistrarImpl.class)
70
71 JUnit with GuiceRule
72 --------------------
73
74 Use \*Module classes which define Object Wiring Binding in a JUnit \* Test class
75 like this:
76
77 .. code:: java
78
79    public @Rule MethodRule guice = new GuiceRule(AclServiceModule.class, AclServiceTestModule.class);
80
81    @Inject DataBroker dataBroker;
82    @Inject AclServiceManager mdsalApiManager;
83    @Inject AclInterfaceStateListener mdsalApiManager;
84
85 Async
86 =====
87
88 In these Component Tests (more so than in simple Unit Tests), one often
89 hits problems due to the extensive use of highly asynchronous code in
90 ODL. Some progress has been made with testing utilities for each
91 respective async API, detailed in this chapter.
92
93 genius AsyncClusteredDataTreeChangeListenerBase & AsyncDataTreeChangeListenerBase
94 ----------------------------------------------------------------------------------
95
96 In order to make a test wait for something which happens in a
97 AsyncClusteredDataTreeChangeListenerBase or
98 AsyncDataTreeChangeListenerBase subclass before then asserting on the
99 outcome of what happened, you just add the
100 TestableDataTreeChangeListenerModule to the GuiceRule of your \*Test,
101 and then @Inject AsyncEventsWaiter asyncEventsWaiter, and use
102 awaitEventsConsumption() AFTER having done action like a data store
103 write for which a listener should kick in, and BEFORE reading the
104 datastore to check the effect:
105
106 .. code:: java
107
108    public final @Rule MethodRule guice = new GuiceRule(
109        new YourTestModule(),
110        new TestableDataTreeChangeListenerModule());
111    @Inject AsyncEventsWaiter asyncEventsWaiter;
112    asyncEventsWaiter.awaitEventsConsumption();
113
114 If a AsyncClusteredDataTreeChangeListenerBase or
115 AsyncDataTreeChangeListenerBase (subclass) has "fired", then the
116 AsyncEventsWaiter verifies that a test has indeed used
117 awaitEventsConsumption() - and fails the test with
118 IllegalStateException: Test forgot an awaitEventsConsumption() if it
119 does not. This mechanism ensures that a test does not "forget" to
120 awaitEventsConsumption and assert an expected outcome. NB however that
121 if the test runs fast, it may end before the listeners kicked in, and
122 the IllegalStateException may not always been seen (i.e. leading to a
123 "heisenbug", found with the RunUntilFailureRule). Therefore, if in your
124 test you do not need to awaitEventsConsumption() at all, then you should
125 not use the TestableDataTreeChangeListenerModule. However, this is
126 likely an indication of lack of better test coverage in your test - you
127 probably do want to assert on the effect of your
128 AsyncClusteredDataTreeChangeListenerBase or
129 AsyncDataTreeChangeListenerBase subclasses?
130
131 infrautils JobCoordinator (formerly genius DataStoreJobCoordinator)
132 ---------------------------------------------------------------------
133
134 similarly to above, using the JobCoordinatorEventsWaiter:
135
136 .. code:: text
137
138    @Inject JobCoordinatorEventsWaiter coordinatorEventsWaiter;
139    coordinatorEventsWaiter.awaitEventsConsumption();
140    (TODO still need to be ported from genius to infrautils)
141    (TODO need to write a combined AsyncEventsWaiter instead of doing e.g. InterfaceManagerTestUtil's waitTillOperationCompletes)
142
143 It is HIGHLY (!) recommended to FIRST switch code from the @Deprecated
144 DataStoreJobCoordinator (in genius) to the JobCoordinator (in
145 infrautils), because that does not suffer from the problem where a
146 background job can "continue on" from one @Test method into another
147 @Test, or even from one \*Test class into another, due to use of
148 "static", which can lead to VERY confusing log messages.
149
150 genius ResourceBatchingManager
151 -------------------------------
152
153 The ResourceBatchingManager API does not yet have an AsyncEventsWaiter
154 companion.
155
156 Other
157 -----
158
159 Some of our "new style" Component Tests, such as e.g.
160 InterfaceManagerConfigurationTest, and others, still need Thread.sleep()
161 in some places.. the eventual goal is to be able to eventually
162 completely eliminate them from all tests.
163
164 Tutorial
165 ========
166
167 Let's imagine you want to make a change e.g. in aclservice, just as an
168 example. Specifically, you've added a new argument for another new
169 internal bean or external service to the @Singleton
170 AclServiceManagerImpl @Inject annotated constructor, let's say to an
171 IdManagerService for the sake of this example discussion.
172
173 A component test based on Guice wiring, such as AclServiceTest, will now
174 fail on you with a message saying something like this:
175
176 *No implementation for (...your new service...) was bound while locating
177 (...) for the X-th parameter of AclServiceManagerImpl.*
178
179 The \*Module classes referenced from the GuiceRule in a \*Test is where
180 the wiring is defined - that's what determines, for that test, what
181 implementation class is bound to what service interface etc. If you have
182 a look at e.g. the AclServiceModule & AclServiceTestModule, it should be
183 obvious what that does - just 1 single line for each binding.
184
185 The error message shown above simply means that an interface was
186 encountered but you have not specified what implementation you would
187 like to use for that interface in a given test. (Different tests could
188 have different Module with varying bindings; but don't have to.)
189
190 To fix this after having made your change, you would now have to add 1
191 line in AclServiceTestModule to do a bind() of IdManagerService to...
192 something.
193
194 If IdManagerServiceas was some new internal helper class of aclservice
195 which you would like to test, then you would just do:
196
197 .. code:: java
198
199    bind(IdManagerService.class).to(YOURIdManagerServiceImpl.class);
200
201 The YOURIdManagerServiceImpl would have a @Singleton annotation on its
202 class, and have an @Inject annotation on its constructor, to
203 automatically get its dependencies injected (and perhaps have
204 @PostConstruct and @PreDestroy, if it has a "lifecycle"; or extend
205 AbstractLifecycle). This is further documented on the `DI
206 Guidelines <https://wiki-archive.opendaylight.org/view/BestPractices/DI_Guidelines>`__
207 page.
208
209 Now, in the case of an existing ODL service from another project, you
210 typically didn't actually write your own implementation of the
211 IdManagerService interface. At full system runtime, you probably would
212 like that to use the IdManager class (and you probably added that to
213 your BP XML). So, having understood above, you COULD now be tempted to
214 add this in AclServiceTestModule:
215
216 .. code:: java
217
218    bind(IdManagerService.class).to(IdManager.class);
219
220 but there is two problems with this, 1. small practical (easy to fix),
221 2. conceptual (more important):
222
223 1. IdManager at the time of initially writing this documentation didnt't
224 have @Singleton @Inject and @PreDestroy on its close() .. this may have
225 already changed - or you could, easily, make a contribution to Genius to
226 change that; I'd recommend making IdManager extend AbstractLifecycle in
227 this case. This can theoretically, even though we wouldn't recommend
228 that, also be worked-around by doing the IdManager "wiring" manually
229 through 2 lines of like new IdManager(...) and then use
230 bind(IdManagerService.class) .toInstance(myIdManager). BUT...
231
232 2. ... it would, typically IMHO, be wrong to use IdManager as
233 IdManagerService implementation in AclServiceTest. This is more of a
234 general recommendation than a hard rule. The idea is that the component
235 test of aclservice should NOT have to depend on the real implementation
236 of all external services the aclservice code depends on (only all of the
237 internal beans of aclservice). So it would, generally, be considered
238 better to bind a local test implementation of IdManager, which does the
239 minimum you need for the test. A full coverage test of IdManager would
240 be the responsibility of genius' idmanager-impl, not aclservice-impl. So
241 what I would probably start with doing in your case, unless there is a
242 very strong need that you must absolutely have the "full" IdManager for
243 the AclServiceTest, is to just put this into AclServiceTestModule's
244 configure() method:
245
246 .. code:: java
247
248    // import static org.opendaylight.yangtools.testutils.mockito.MoreAnswers.*;
249    bind(IdManagerService.class).toInstance(Mockito.mock(IdManagerService.class, exception());
250
251 Doing this will resolve the Guice Exception you have run into below. But
252 whenever some aclservice code now actually calls a method
253 IdManagerService, you'll get an UnstubbedMethodException .. and this is
254 normal - because you just mocked IdManagerService! I would still
255 recommend to start like this, and then go about fixing
256 UnstubbedMethodException as they arise when you run AclServiceTest ...
257
258 Let's for example say that your new code calls IdManagerServices'
259 allocateIdRange() method somewhere - I don't know if it does, so this is
260 just for Illustration. You could make your mocked IdManagerService do
261 something else than throw a UnstubbedMethodException for
262 allocateIdRange() in two different "styles", this is somewhat dependant
263 on personal preference:
264
265 A) Write out a partial "fake" implementation of it:
266
267 Write an inner class right there inside at the end of the
268 AclServiceTestModule.java - just because it's easier to have this
269 together and immediately evident when reading code; unless it becomes
270 very long, in which case you could also move it outside, of course:
271
272 .. code:: java
273
274    private abstract static class TestIdManagerService implements IdManagerService {
275        @Override
276        public Future<RpcResult<AllocateIdOutput>> allocateId(AllocateIdInput input) {
277        // TODO do something minimalistic here, just useful for the test, not a general implementation
278        }
279    }
280
281 Note that the code in such test service implementations are typically
282 simplistic and trivial, and not "real full fledged". Note also that only
283 methods which the test actually requires are implemented; because it's
284 abstract, we don't have to write anything at all for other methods of
285 the interface.
286
287 You can then change the binding in configure() to be:
288
289 .. code:: text
290
291    bind(IdManagerService.class).toInstance(Mockito.mock(TestIdManagerService.class, realOrException())
292
293 Note the subtle difference with the use of realOrException() instead of
294 just exception().
295
296 This first style is vorburger's personal preference; finding this code
297 clearer to read and understand for anyone than "traditional" Mockito
298 usage, and not minding to have to type a few extra lines (for the
299 class), which the IDE will put for me on Ctrl-Space anyway, than having
300 to understand the Mockito magic. This is particular true when the
301 implemented methods have anything but non-trivial arguments and return
302 types - which is often the case in ODL.
303
304 B) Write the implementation using traditional Mockito API:
305
306 Write a method, just for clarify, such as:
307
308 .. code:: java
309
310    private IdManagerService idManagerService() {
311        IdManagerService idManagerService = Mockito.mock(IdManagerService.class);
312        Mockito.when(idManagerService.allocateId(...)).thenReturn(...);
313        // etc.
314        return idManagerService;
315    }
316
317 and then changing the binding in configure() to be:
318
319 .. code:: java
320
321    bind(IdManagerService.class).toInstance(idManagerService());