I'm currently doing some work in Java but it has been a few years (maybe 3?) since the last time I worked with this language. As part of the project I am working on we are developing an implementation of a standard protocol that runs over HTTP (ahem... Trying not to be too specific) and we are building the back-end services in Java as REST APIs. The whole thing is tied together with Spring.
Welcome back to Mocking
First of all the guys before me have configured the projects to use Mockito instead of EasyMock (which I am used to). Rather than go drag in EasyMock I thought I'd give it a go and it does seem pretty good and makes for readable tests. The thing I really like are the annotations which make mocking in your tests much easier. Instead of saying
private GarbageService fixture = mock(GarbageService.class);
You can instead say
@Mock GarbageService fixture;
And Mockito gets the hint. Well it gets the hint if you tell it to go fill all the mocks which you can either do by including a call to:
MockitoAnnotions.initMocks(this)
Or by running the unit-test in the MockitoTestRunner@RunWith(MockitoJUnitRunner.class)
class TestGarbageService
{
...
Spring Autowiring
Now we are using Spring but previously the team just used the XML technique for tying together dependencies. This is nice as it minimizes the amount of Spring in your code but on the other hand maintaining the XML file is a pain. Also, I hate how people put data initialization in the XML file as this means you have actual business logic embedded in the XML and not the Java.
I discovered a Spring annotation called @Autowired which you can tag either a setter method, a constructor or even a data member so it will be automatically initialized by Spring. As I said my project is a Servlet so this took a few goes to get working. First of all I created a spring context and enabled searching for implementation by type. You have to specify a base package so I specified the root of my project:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd" >
<context:component-scan
base-package="com.mycompany.myproject">
</context:component-scan>
</beans>
This was the first Spring I had put in the servlet so I had to edit the servlet web.xml to enable Spring:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<display-name>My Project</display-name>
<description>My Project Servlet</description>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-config.xml</param-value>
</context-param>
...
This wasn't sufficient so after some googling I discovered I probably need a listener also which I added to the bottom of the web.xml
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
Now I didn't want to use DispatchServlet as I wanted more control over the HTTP. The protocol I am implementing is specific on formatting, content-type etc and it seemed better to do this using a regular servlet. So then I created a ServiceLocator interface that in turn provided all the interfaces the Servlet itself needs. This ServiceLocator is instantiated via Spring and so all of the services then get injected as dependencies. In the init() method of the servlet I do this:
WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(
getServletContext());
this.serviceLocator = ctx.getBean("ServiceLocator", ServiceLocator.class);
Ok so this didn't work the first time (or the 10th). Eventually I figured out that in order for the implementations of the interfaces to be found, they have to be tagged. There are a few annotations you can use but the simplest worked for me:
@Component
public class ServiceLocatorImpl : implements ServiceLocator
{
...
And then I can instantiate the ServiceLocator! I had to do the same to all the dependent interfaces but when i did I was able to get this running.
Testing Autowired Code
The next trick was getting my unit-test to work when the dependencies had been Autowired. Usually you would create mocks of the dependent interfaces and pass them into the class under test either via the constructor or a setter but in this case there is no method to do that. I could go back to creating setter methods and tag these as Autowired but thankfully the newer versions of Mockito (1.8.4 and up) have a better solution for this called @InjectMocks
The way this seems to work is that it will inject mocks into the class under test for any members tagged as requiring auto-wiring. The mocks are drawn from mocks declared in your test class. Lets pretend my service depends on an interface called GarbageDAO which is autowired into the ServiceLocator. In the test code I could do this
@Mock GarbageDAO mockDao;
@InjectMocks ServiceLocator fixture = new ServiceLocatorImpl();
The effect is that the ServiceLocator implementation will be injected with the mock GarbageDAO. You can write expectations on the mock and when you call the fixture these should get triggered.
when(mockDao.fetchGarbage()).thenReturn(mockGarbage);
fixture.takeOutGarbage();
Factory
I have this class that acts as a factory for a handler depending on the type of protocol request received. I was trying to figure out how to make this work with Spring. Using XML it is easy as I can just create beans for each handler and pass them via a series of add calls to the factory.
Using the auto-wired method is not so easy. The problem is that as they all implement the same interface there is no telling them apart.
I figured out you can specify a name when you declare them as components and then later you can use this name as a qualifier when auto-wiring. There are more complex forms of qualifier but this did the job for me.
So in my factory I have:
@Autowired
@Qualifier("RetrieveCollectionSchedule")
GarbageHTTPHandler collectionScheduleHandler;
@Autowired
@Qualifier("CollectGarbage")
GarbageHTTPHandler collectionHandler;
Then when I declare the implementations I do this:
@Component("CollectGarbage")
public class CollectionHandler : implements GarbageHTTPHandler
The factory then uses the request type to determine which handler is required and returns it from its list of members.
Testing this was a bit harder. The first time I tried this the @InjectMocks didn't work. After reading a bit about this it turns out the mock injector will first try to match by type and THEN matches by name. So to make this work in my test class I had to make the name of the mock match the name of the member in the factory and then it worked:
@Mock GarbageHTTPHandler collectionScheduleHandler;
@Mock GarbageHTTPHandler collectionHandler;
@InjectMocks GarbageHandlerFactory fixture = new GarbageHandlerFactoryImpl();
No comments:
Post a Comment