Menu

#192 dependency Injections into Servlets (For Mocking)

open
nobody
7
2016-10-23
2016-10-23
Slava D
No

I've been using HttpUnit for a while to unit test HttpServlets.
Now, I'm trying to mock a class that the servlet is using. Currently, it's very awkward and the code is hard to read (invoking with dummy request to get the invocation-context and access the created servlet).

I'd like to be ebale to inject a mock dependency to a servlet when run from HttpUnit's ServletRunner.

Thank you!

Discussion

  • Russell Gold

    Russell Gold - 2016-10-23

    Can you explain the problem? Maybe a code sample?

     
  • Slava D

    Slava D - 2016-10-23

    I'm writing a servlet (in this example HelloWorldServlet) which sole purpose is to "handle network related logics" and then use some "BusinessLogicsService" to serve the business logics.

    Here is example code:

    HelloWorldServlet.java

    public class HelloWorldServlet extends HttpServlet {
        private static final Logger LOG = LoggerFactory.getLogger(HelloWorldServlet.class);
        private BusinessLogicsService  businessLogicsService;
    
        private void  setBusinessLogicsService(BusinessLogicsService  businessLogicsService) {
            this.businessLogicsService = businessLogicsService;
        }
    
        @Override
        public void init() throws ServletException {
            super.init();
            businessLogicsService = new BusinessLogicsService();
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            ApiResponse apiResponse = new ApiResponse();
            SomeRequest someRequest;
            try {
                someRequest = CollectRequest.fromString(req.getReader().lines()
                    .collect(Collectors.joining()), SomeRequest.class);
                if (someRequest == null) {
                    throw new IllegalArgumentException("Data was empty");
                }
                businessLogicsService.parse(someRequest);
                resp.setContentType("application/json;charset=utf-8");
                resp.setStatus(200);
                apiResponse = new ApiResponse()
                    .withStatus(ApiResponse.ApiStatus.SUCCESS)
                    .withPayload("Hello World");
            } catch (Exception e) {
                LOG.trace("Empty req was sent");
                apiResponse = apiResponse.withStatus(ApiResponse.ApiStatus.ERROR)
                    .withError("Empty Body");
                resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            } finally {
                resp.getWriter().print(apiResponse.toString());
            }
        }
    
    }
    

    BusinessLogicsService.java

    public class BusinessLogicsService {
    
        public void parse(SomeRequest request) {
            /**
             * Some logics here
             */
        }
    }
    

    When instantiating the test, it looks like this:

            ServletRunner servletRunner = new ServletRunner();
            servletRunner.registerServlet("hello", HelloWorldServlet.class.getName());
    

    The servlet container creates an instance of HelloWorldServlet using the NoArgsConstructor.
    Which means, I can not access the servlet directly and inject my mock dependency.

    My purpose now is to write a UNIT test for 'HelloWorldServlet'.
    I want to be able to test the servlet only by injecting a Mock SomeService instance from the tests (for example a mock created by Mockito framework).

    A solution I found was invoking a Dummy request, catching the InvocationContext, getting the servlet and changing it in runtime. This way is messy and not ellegant.

    Can you offer a better way to inject such dependency?

     
  • Russell Gold

    Russell Gold - 2016-10-23

    Sure. I do things like this all the time, using the SimpleStub framework (http://simplestub.meterware.com)

    First, create a factory class for your business service, using an interface:

    interface BusinessLogicsServiceFactory {
    BusinessLogicsService createBusinessLogicsService();
    }

    and use it in your servlet like this:

    :

    private BusinessLogicsService businessLogicsService;
    private static BusinessLogicsServiceFactory factory = new BusinessLogicsServiceFactory() {
    BusinessLogicsService createBusinessLogicsService() {
    return new BusinessLogicsService();
    }
    }

    @Override
    public void init() throws ServletException {
        super.init();
        businessLogicsService = factory.createBusinessLogicsService();
    }
    
    so far, this looks just the same, right?
    

    But now, in your unit test setup (before the servlet is initiialized), you can do this:

    private Memento memento;

    @Before
    public void setUp() throws Exception {
    memento = StaticStubSupport.install(HelloWorldServlet.class, "factory", new TestFactory());
    }

    @After
    public void tearDown() {
    memento.revert();
    }

    Now when the test is run, it will use the test version of the factory, which creates a fresh copy of the test version of the business logic. The tearDown puts back the original version (because it is good practice for unit tests to clean up after themselves).

    You may, BTW, find SimpleStub a much easier way of creating test doubles than mocks, as you can use normal Java code to do explictly what you want, rather than a complex series of library calls.

     

Log in to post a comment.