Mock-Testing a Magnolia Servlet Filter with Mockito

By on

Magnolia CMS is an Open Source CMS. Recently one of my clients asked me to help out on a project which is around that. Nobody from our team had a big clue with it and curious as I am I took the chance to look into this little beast. I have to admit, I was first pretty much overwhelmed. I am still overwhelmed but feel better from day to day. After all I have learned that CMS development is not what I want to do for the rest of my life. Except, maybe developing the CMS itself, as there is much great technology in back.

Basically we created a Custom Filter and hooked into the Request Chain. When you understood how you can commit this configuration change into your own module, you are pretty far. For this blog post I assume you have already managed to set up your project and your filter and just want to test it.

First, enable Mockito in your project (shown here with Maven):

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.9.5</version>
    <scope>test</scope>
</dependency>

Now let's look at my filter:

public class MyFilter extends AbstractMgnlFilter {
    private MyClass myClass = MyClass.getInstance();

    public void setMyClass(MyClass myClass) {
        this.myClass = myClass;
    }

    public void doFilter(
       HttpServletRequest request, 
       HttpServletResponse response, 
       FilterChain chain)
            throws IOException, ServletException {
       MgnlContext.getInstance().setAttribute("myResult", myClass.getData(), Context.LOCAL_SCOPE);
       chain.doFilter(request, response);
    }
}

It's easy. In this case I am creating an instance of MyClass via a factory. This class will be used later to return some data from a remote repository and put it into the Magnolia Context. The scope is local, which means: live happy for this single request.

In practice there is way much more code around this. In reality my filter should not run on every request (it runs even for requests which call css/javascript files now). Now how would you test this?

The answer is simple. You can use JUnit and Mockito. With that powerful combination it is just great. I have to admit that I looked for the first time into Mockito but it will stay on my list of everyday tools.

The following code is from my @Test method. First I am creating some mocks which help me with the usual Servlet things:

HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletResponse response = mock(HttpServletResponse.class);
FilterChain chain = mock(FilterChain.class);

I need these objects for completing the filter chain. Then I create the filter instance.

MyFilter filter = new MyFilter();
MyClass myClass = MyClassMock.getInstance();
filter.setMyClass(myClass);

As you have seen above, I created a special setter to put my Data-Access-Class. I have tried it with Google Guice Dependency Injection first, but it was not worth the effort. Filters in Magnolia extend AbstractMgnlFilter and you would have to hook into Magnolia itself to use custom filters with Guice. I preferred something simpler, added a setter and put a special Mock class into it. Of course you can use Mockito here too. In my case, this was the better approach.

I should just note: Magnolia 4.5 does support Guice 3. That I have chosen not to find out how I can hook in my Data-Class is just a matter of time. I can imagine that with the new Guice features Magnolia can impress some people.

And here we are going to connect these filters:

FilterConfig filterConfig = mock(FilterConfig.class);
ServletContext context = mock(ServletContext.class);
when(filterConfig.getServletContext()).thenReturn(context);

The FilterConfig needs to return a ServletContext, both mocked by Mockito. The "when" Function is a static import from Mockito.

I have to put some parameters into my request as these were evaluated by my Data-Provider "MyClass".

when(request.getParameter("requestParam")).thenReturn("value");
Map parameterMap = new HashMap();
parameterMap.put("requestParam", new String[] { "value" });
when(request.getParameterMap()).thenReturn(parameterMap);

Heads up. I did not only stub the "getParameter" method which will return the String "value" when I call it with the key "requestParam". It is most likely also required to stub the ParameterMap which is in fact a simple HashMap. In real live it is some special locking enabled HashMap from Catalina, if you use Tomcat. But as long as it implements the Map-Interface it's great for our purposes.

Now here comes some magic:

AbstractContext ctx = new AbstractContext()  {};
ctx.setAttributeStrategy(new MapAttributeStrategy());

MgnlContext.setInstance(ctx);

filter.init(filterConfig);
filter.doFilter(request, response, chain);

Object value = MgnlContext.getInstance().getAttribute("myResult");
Assert.assertEquals(  )

When working with our Filter, we want to put something into the Magnolia Context. For our test we need to create that Context as we do not have a running instance of Magnolia in our CI server. This is done with "implementing" the AbstractContext. In fact, the AbstractContext is already pretty complete. I do not need to do anything and why it is called abstract... well. Not to deep into that. I have first tried to use the SimpleContext, which implies to me it is a ready to use Context. But it is not the case. The SimpleContext does also check in its constructor if there is already an context, which is bad. it looks like that:

public SimpleContext() {
   this(MgnlContext.getInstance());
}

Well, with that the SimpleContext was not usable and I went with the abstract one. The only requirement I needed was to set a MapAttributeStrategy. It would enable my context to store parameters in an HashMap.

Once my context was created, I could put it into the Magnolia Context. That done I initialized my filter and finally called the doFilter method which did some magic and stored the result in the context. As there is a static getInstance Method on the Magnolia Context it is easy to read the result and assert it with JUnit.

Tags: Java, Magnolia, Open Source