Component Tests (with Guice)¶
Note: This document is a work in progress.
Introduction¶
Code which uses dependency injection with standard annotations is well suite for component tests.
Component tests cover the functionality of 1 single OpenDaylight bundle (e.g.
netvirt aclservice
, genius interfacemanager
, etc.) They are focused
on end-to-end testing of APIs, not individual internal implementation
classes (that’s what Unit Tests are for). They focus on testing the code
in their own module, and typically stub required external services. They
assert outcome on the system, often those external services, and the
data store. They wire together the internal beans through a Dependency
Injection (DI) Framework called Guice, which leverages standard Java
annotations in the code, which is equally used by Blueprints.
This presentation from the ODL Summit 2016 has some content related to this topic.
Maven¶
In order to use Google Guice in your end2end API component tests to wire bean objects using the same annotations as BP, use:
<dependency>
<groupId>org.opendaylight.infrautils</groupId>
<artifactId>inject.guice.testutils</artifactId>
<version>${infrautils.version}</version>
<scope>test</scope>
</dependency>
Code¶
Object Wiring Binding¶
You can write Guice object binding wiring classes like this:
public class AclServiceModule extends AbstractGuiceJsr250Module {
@Override
protected void configureBindings() {
bind(AclServiceManager.class).to(AclServiceManagerImpl.class);
bind(AclInterfaceStateListener.class);
}
...
}
for any OSGi service external to the bundle (not local bean) you use bind() like this:
bind(DataTreeEventCallbackRegistrar).annotatedWith(OsgiService.class).to(DataTreeEventCallbackRegistrarImpl.class)
JUnit with GuiceRule¶
Use *Module classes which define Object Wiring Binding in a JUnit * Test class like this:
public @Rule MethodRule guice = new GuiceRule(AclServiceModule.class, AclServiceTestModule.class);
@Inject DataBroker dataBroker;
@Inject AclServiceManager mdsalApiManager;
@Inject AclInterfaceStateListener mdsalApiManager;
Async¶
In these Component Tests (more so than in simple Unit Tests), one often hits problems due to the extensive use of highly asynchronous code in ODL. Some progress has been made with testing utilities for each respective async API, detailed in this chapter.
genius AsyncClusteredDataTreeChangeListenerBase & AsyncDataTreeChangeListenerBase¶
In order to make a test wait for something which happens in a
AsyncClusteredDataTreeChangeListenerBase or
AsyncDataTreeChangeListenerBase subclass before then asserting on the
outcome of what happened, you just add the
TestableDataTreeChangeListenerModule to the GuiceRule of your *Test,
and then @Inject AsyncEventsWaiter asyncEventsWaiter
, and use
awaitEventsConsumption()
AFTER having done action like a data store
write for which a listener should kick in, and BEFORE reading the
datastore to check the effect:
public final @Rule MethodRule guice = new GuiceRule(
new YourTestModule(),
new TestableDataTreeChangeListenerModule());
@Inject AsyncEventsWaiter asyncEventsWaiter;
asyncEventsWaiter.awaitEventsConsumption();
If a AsyncClusteredDataTreeChangeListenerBase or
AsyncDataTreeChangeListenerBase (subclass) has “fired”, then the
AsyncEventsWaiter verifies that a test has indeed used
awaitEventsConsumption()
- and fails the test with
IllegalStateException: Test forgot an awaitEventsConsumption()
if it
does not. This mechanism ensures that a test does not “forget” to
awaitEventsConsumption
and assert an expected outcome. NB however that
if the test runs fast, it may end before the listeners kicked in, and
the IllegalStateException may not always been seen (i.e. leading to a
"heisenbug"
, found with the RunUntilFailureRule). Therefore, if in your
test you do not need to awaitEventsConsumption()
at all, then you should
not use the TestableDataTreeChangeListenerModule. However, this is
likely an indication of lack of better test coverage in your test - you
probably do want to assert on the effect of your
AsyncClusteredDataTreeChangeListenerBase or
AsyncDataTreeChangeListenerBase subclasses?
infrautils JobCoordinator (formerly genius DataStoreJobCoordinator)¶
similarly to above, using the JobCoordinatorEventsWaiter:
@Inject JobCoordinatorEventsWaiter coordinatorEventsWaiter;
coordinatorEventsWaiter.awaitEventsConsumption();
(TODO still need to be ported from genius to infrautils)
(TODO need to write a combined AsyncEventsWaiter instead of doing e.g. InterfaceManagerTestUtil's waitTillOperationCompletes)
It is HIGHLY (!) recommended to FIRST switch code from the @Deprecated DataStoreJobCoordinator (in genius) to the JobCoordinator (in infrautils), because that does not suffer from the problem where a background job can “continue on” from one @Test method into another @Test, or even from one *Test class into another, due to use of “static”, which can lead to VERY confusing log messages.
genius ResourceBatchingManager¶
The ResourceBatchingManager API does not yet have an AsyncEventsWaiter companion.
Other¶
Some of our “new style” Component Tests, such as e.g. InterfaceManagerConfigurationTest, and others, still need Thread.sleep() in some places.. the eventual goal is to be able to eventually completely eliminate them from all tests.
Tutorial¶
Let’s imagine you want to make a change e.g. in aclservice
, just as an
example. Specifically, you’ve added a new argument for another new
internal bean or external service to the @Singleton
AclServiceManagerImpl @Inject annotated constructor, let’s say to an
IdManagerService for the sake of this example discussion.
A component test based on Guice wiring, such as AclServiceTest, will now fail on you with a message saying something like this:
No implementation for (…your new service…) was bound while locating (…) for the X-th parameter of AclServiceManagerImpl.
The *Module classes referenced from the GuiceRule in a *Test is where the wiring is defined - that’s what determines, for that test, what implementation class is bound to what service interface etc. If you have a look at e.g. the AclServiceModule & AclServiceTestModule, it should be obvious what that does - just 1 single line for each binding.
The error message shown above simply means that an interface was encountered but you have not specified what implementation you would like to use for that interface in a given test. (Different tests could have different Module with varying bindings; but don’t have to.)
To fix this after having made your change, you would now have to add 1 line in AclServiceTestModule to do a bind() of IdManagerService to… something.
If IdManagerServiceas was some new internal helper class of aclservice
which you would like to test, then you would just do:
bind(IdManagerService.class).to(YOURIdManagerServiceImpl.class);
The YOURIdManagerServiceImpl would have a @Singleton annotation on its
class, and have an @Inject annotation on its constructor, to
automatically get its dependencies injected (and perhaps have
@PostConstruct
and @PreDestroy
, if it has a "lifecycle"
; or extend
AbstractLifecycle). This is further documented on the DI
Guidelines
page.
Now, in the case of an existing ODL service from another project, you typically didn’t actually write your own implementation of the IdManagerService interface. At full system runtime, you probably would like that to use the IdManager class (and you probably added that to your BP XML). So, having understood above, you COULD now be tempted to add this in AclServiceTestModule:
bind(IdManagerService.class).to(IdManager.class);
but there is two problems with this, 1. small practical (easy to fix), 2. conceptual (more important):
1. IdManager at the time of initially writing this documentation did not
have @Singleton
@Inject
and @PreDestroy
on its close()
.. this may have
already changed - or you could, easily, make a contribution to Genius to
change that; I would recommend making IdManager extend AbstractLifecycle in
this case. This can theoretically, even though we wouldn’t recommend
that, also be worked-around by doing the IdManager “wiring” manually
through 2 lines of like new IdManager(...)
and then use
bind(IdManagerService.class)
.toInstance(myIdManager)
. BUT…
2. … it would, typically IMHO, be wrong to use IdManager as
IdManagerService implementation in AclServiceTest. This is more of a
general recommendation than a hard rule. The idea is that the component
test of aclservice
should NOT have to depend on the real implementation
of all external services the aclservice
code depends on (only all of the
internal beans of aclservice
). So it would, generally, be considered
better to bind a local test implementation of IdManager, which does the
minimum you need for the test. A full coverage test of IdManager would
be the responsibility of genius idmanager-impl
, not aclservice-impl
. So
what I would probably start with doing in your case, unless there is a
very strong need that you must absolutely have the “full” IdManager for
the AclServiceTest, is to just put this into AclServiceTestModule’s
configure() method:
// import static org.opendaylight.yangtools.testutils.mockito.MoreAnswers.*;
bind(IdManagerService.class).toInstance(Mockito.mock(IdManagerService.class, exception());
Doing this will resolve the Guice Exception you have run into below. But
whenever some aclservice
code now actually calls a method
IdManagerService, you’ll get an UnstubbedMethodException - and this is
normal - because you just mocked IdManagerService! I would still
recommend to start like this, and then go about fixing
UnstubbedMethodException as they arise when you run AclServiceTest …
Let us for example say that your new code calls IdManagerServices
allocateIdRange()
method somewhere - I don’t know if it does, so this is
just for Illustration. You could make your mocked IdManagerService do
something else than throw a UnstubbedMethodException for
allocateIdRange()
in two different “styles”, this is somewhat dependant
on personal preference:
Write out a partial “fake” implementation of it:
Write an inner class right there inside at the end of the AclServiceTestModule.java - just because it’s easier to have this together and immediately evident when reading code; unless it becomes very long, in which case you could also move it outside, of course:
private abstract static class TestIdManagerService implements IdManagerService {
@Override
public Future<RpcResult<AllocateIdOutput>> allocateId(AllocateIdInput input) {
// TODO do something minimalistic here, just useful for the test, not a general implementation
}
}
Note that the code in such test service implementations are typically simplistic and trivial, and not “real full fledged”. Note also that only methods which the test actually requires are implemented; because it’s abstract, we don’t have to write anything at all for other methods of the interface.
You can then change the binding in configure() to be:
bind(IdManagerService.class).toInstance(Mockito.mock(TestIdManagerService.class, realOrException())
Note the subtle difference with the use of realOrException()
instead of
just exception()
.
This first style is Vorburger’s personal preference; finding this code
clearer to read and understand for anyone than “traditional” Mockito
usage, and not minding to have to type a few extra lines (for the
class), which the IDE will put for me on Ctrl-Space
anyway, than having
to understand the Mockito magic. This is particular true when the
implemented methods have anything but non-trivial arguments and return
types - which is often the case in ODL.
Write the implementation using traditional Mockito API:
Write a method, just for clarify, such as:
private IdManagerService idManagerService() {
IdManagerService idManagerService = Mockito.mock(IdManagerService.class);
Mockito.when(idManagerService.allocateId(...)).thenReturn(...);
// etc.
return idManagerService;
}
and then changing the binding in configure() to be:
bind(IdManagerService.class).toInstance(idManagerService());