Chapter 4. Resolving Out-of-container Dependencies with Mockito

The preceding chapter covered the container Spring integration testing and the Spring testing framework. This chapter deals with the role of the Mockito framework in Spring unit testing and how to resolve container dependency with Mockito. The following topics are covered in depth in this chapter:

  • Unit testing the service layer with Mockito
  • Unit testing the DAO layer with Mockito
  • Unit testing the web layer with Mockito

Enterprise applications change over time. There are several reasons for change, such as the addition of new features, bug fixing, improvement in the non functional requirements such as performance or scalability, regulatory changes such as ICD-10 (ICD-10 is the 10th revision of the International Statistical Classification of Diseases and Related Health Problems (ICD), a medical classification list by the World Health Organization (WHO)), adapting to modern technology such as implementing JPA, and so on. It doesn't matter how good a software system is, it will be transformed over time. However, a loosely coupled system is more resilient to change than a rigid system. In a tightly coupled system, when we modify a part of the system, the other parts of the system break and we need to fix those parts. This in turn increases the complexity and the degree of reworking required. We should always strive for loose coupling. To minimize coupling, we can divide our system into multiple layers, such as the data access layer, controller layer, service layer, and so on. Once we implement the layers, we can localize the change in one layer without affecting the other layers, such that we can change the data access implementation from Spring JDBC to Hibernate without affecting the service layer.

We'll build a layered Spring web application and unit test each layer. We'll start with the presentation layer and go over to the service and data access layers.

Unit testing the web layer

We'll build a simple Spring web application with the following functionalities:

  • User registration
  • User login

We'll create the following three layers:

  • A data access layer to store and retrieve data
  • A service layer to perform business logic and data validation
  • Spring controllers to present the UIs and invoke services

In this section, we'll build the controllers and unit test them in isolation from the web server. We have to mock out the service and data access logic.

Perform the following steps to build the web application:

  1. Create a dynamic web project, SpringWeb, and copy the Spring JARs from the Spring MVC project we created in Chapter 1, Getting Familiar with the Spring Framework.
  2. Add the following lines to the web.xml file in order to configure Spring MVC. We have already covered the details in Chapter 1, Getting Familiar with the Spring Framework:
    <web-app xmlns:xsi="...">
      <display-name>SpringWeb</display-name>
      <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>
          org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
      </servlet-mapping>
      <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
          /WEB-INF/dispatcher-servlet.xml
        </param-value>
      </context-param>
    </web-app>
  3. Create a dispatcher-servlet.xml file under /WEB-INF to load the web application context, and add the following line to the file in order to read the bean definitions from a classpath application context file called beans.xml:
      <import resource="classpath:beans.xml"/>
  4. In the source folder, create an XML file called beans.xml to define the beans. Add the following lines to the file:
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:context="http://www.springframework.org/schema/context"
      xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
      xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c"
      xsi:schemaLocation="
        http://www.springframework.org/schema/beans     
        http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-4.1.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx-4.1.xsd 
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd">
      <mvc:annotation-driven />
      <context:component-scan base-package="com.packt" />
      <bean
        class="org.springframework.web.servlet.view.
            InternalResourceViewResolver">
        <property name="prefix">
          <value>/WEB-INF/pages/</value>
        </property>
        <property name="suffix">
          <value>.jsp</value>
        </property>
      </bean>

    The preceding XML code tells the Spring container to scan the com.packt package for bean definitions. MVC is annotation driven and also defines a Spring view resolver bean. The view resolver embodies that a logical view name should be mapped to a physical .jsp file under the /WEB-INF/pages folder.

  5. Create a login.jsp page under /WEB-INF/pages. Add the following lines to create a login form using the Spring tag library defined in uri="http://www.springframework.org/tags/form" and to display a hyperlink for new user sign-up:
    <%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form"%>
      <div>
      <h2>Login </h2>
      <sf:form method="POST" action="/SpringWeb/onLogin">
      <fieldset>
        <table cellspacing="0">
          <tr>
            <th><label for="userId">User Id:</label></th>
            <td><input type="text" name="userId" 
              id="userId" size="10" maxlength="10"/> </td>
          </tr>
          <tr>
            <th><label for="password">Password:</label></th>
            <td><input type="password" name="password" 
              id="password" size="10" maxlength="10"/></td>
          </tr>
          <tr>
            <td colspan="2">
              <input type="submit" value="Submit" /></td>
          </tr>
        </table>
      </fieldset>
      </sf:form>
      </div>
      <h3><a href="/SpringWeb/register">Sign Up</a></h3>

    Note that the form action is "/SpringWeb/onLogin", which means that when the form is submitted, a Spring controller method annotated with @RequestMapping({ "/onLogin" }) will handle the processing of the request. The sf tag is defined in Spring's taglib and sf:form represents an HTML form tag.

  6. Create a register.jsp page under /WEB-INF/pages to display user registration. A user can enter the login name, password, first name, and last name, and they also click on the login page hyperlink to go back to the login page. This is how the code for the page will look:
      <div>
      <h2>Register User</h2>
      <sf:form method="POST" action="/SpringWeb/onRegistration">
      <fieldset>
    <table cellspacing="0">
      <tr>
        <th><label for="userId">User Id:</label></th>
        <td><input type="text" name="userId" 
          id="userId" size="10" maxlength="10"/></td>
      </tr>
      <tr>
        <th><label for="password">Password:</label></th>
        <td><input type="password" name="password" 
          id="password" size="10" maxlength="10"/></td>
      </tr>
      <tr>
        <th><label for="fname">First Name:</label></th>
        <td><input type="fname" name="fname" 
          id="fname" size="20" maxlength="20"/></td>
      </tr>
      <tr>
        <th><label for="lname">Last Name:</label></th>
        <td><input type="lname" name="lname" 
          id="lname" size="20" maxlength="20"/></td>
      </tr>
      <tr>
        <td colspan="2"><input type="submit" 
              value="Submit" /></td>
      </tr>
    </table>
    </fieldset>
    </sf:form>
    </div>
    <h3><a href="/SpringWeb/login">Login</a></h3>
  7. We'll create a controller class to display the initial login page and handle the login form submission. Create a LoginController class under the com.packt.controller package to handle the user login. The onStartUp method will return a view named login to display the login.jsp page, and the method will be annotated with @RequestMapping({ "/", "/login" }), which signifies that when a user enters the context path to the browser (the / symbol), the login.jsp page is loaded. Also, from the registration page, the user can click on the login ("/login") hyperlink to come back to the login page. The following code snippet shows the LoginController class:
      @Controller
      public class LoginController implements Serializable {
      private static final long serialVersionUID = 1L;
        
        @RequestMapping({ "/", "/login" })
        public String onStartUp(ModelMap model) {
          return "login";
        }
      }

    The @Controller annotation signifies that the class is a Spring controller.

  8. Create an onLogin method to handle the login form submission, and annotate the method with @RequestMapping({ "/onLogin" }) as we defined the form action on the login page. The method has to validate the username and password against a stored value (the database table). We'll create a request-scoped service to read the user ID and password from the request and then validate the same against the database. We'll call the service LoginService. This service will define an isValid() method to validate the user credentials. Make the following change to Spring's application context in order to scope the service request and read the user ID and password from the request for validation:
    <bean id="loginService" 
        class="com.packt.controller.LoginService" 
        scope="request" 
        p:userId="#{request.getParameter('userId')}"
        p:password="#{request.getParameter('password')}">
        <aop:scoped-proxy />
    </bean>

    We have already covered request-scoped beans, so we will not explain them here again. Make the following changes to the controller class:

      @RequestMapping({ "/onLogin" })
      public ModelAndView onLogin(ModelMap model) {
        if (!loginService.isValid()) {
          model.addAttribute("error", "Invalid user name and 
              password");
          return new ModelAndView("login", model);
        }
    
        String userName = loginService.retrieveName();
        model.addAttribute("name", "Welcome "+userName+"!");
        return new ModelAndView("greetings", model);
      }

    If the login fails, it builds an error message that says invalid username or password. Otherwise, LoginService retrieves the username for the logged-in user and builds a greeting message.

  9. Now, create another controller class to handle the user registration. We'll call this class RegistrationController. We need a service to handle user registration, so create a request-scoped service, RegistrationService, to read the userId, password, firstName, and lastName values, and then validate whether the user ID exists or not. Update the application context to register the request-scoped service, as follows:
    <bean id="registrationService" 
      class="com.packt.controller.RegistrationService"
      scope="request" 
      p:userId="#{request.getParameter('userId')}"
      p:password="#{request.getParameter('password')}"
      p:firstName="#{request.getParameter('fname')}"
      p:lastName="#{request.getParameter('lname')}">
      <aop:scoped-proxy />
    </bean>

    Add a showRegisterView method and annotate it with @RequestMapping({ "/register" }) to display the registration page, as shown here:

    @RequestMapping({ "/register" })
    public String showRegisterView(ModelMap model) {
      return "register";
    }

    Add another method, onRegistration, to handle the user registration action. We will use the following method:

    @RequestMapping({ "/onRegistration" })
    public ModelAndView onRegistration(ModelMap model) {
      String error = registrationService.hasError();
      if(error != null){
        model.addAttribute("message", "Cannot create the 
           user due to following error ="+error);
      }else{
        model.addAttribute("message", "User created");
      }
       return new ModelAndView("register", model); 
    }

    This method delegates the user input validation task to the service; the service returns an error if the user ID exists and then the controller shows the error message to the user, otherwise the user is created.

  10. We have created the controller classes, and now we can unit test the controller methods. We'll mock out the services using Mockito. Create a source package, test, for holding the test files and create a com.packt.controller package under test. Add a JUnit test, LoginControllerTest, under com.packt.controller. We need to unit test an invalid login and a successful login scenario. We'll add two tests, as follows:
    import static org.mockito.Mockito.when;
    import static org.junit.Assert.*;
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.mockito.Mock;
    import org.mockito.runners.MockitoJUnitRunner;
    import org.springframework.ui.ModelMap;
    import org.springframework.web.servlet.ModelAndView;
    
    @RunWith(MockitoJUnitRunner.class)
    public class LoginControllerTest {
      @Mock
      private LoginService loginService;
      private LoginController controller;
      
      @Before
      public void setup(){
        controller = new LoginController();
        controller.setLoginService(loginService);
      }
      
      @Test
      public void when_invalid_login_error_message_is
            _generated() {
        when(loginService.isValid()).thenReturn(false);
        ModelMap model = new ModelMap();
        ModelAndView modelAndView = controller.onLogin(model);
        assertNotNull(modelAndView.getModel().get("error"));
        assertEquals("login", modelAndView.getViewName());
      }
      
      @Test
      public void when_a_valid_login_greeting_message_
            is_generated() {
        when(loginService.isValid()).thenReturn(true);
        ModelMap model = new ModelMap();
        ModelAndView modelAndView = controller.onLogin(model);
        assertNull(modelAndView.getModel().get("error"));
        assertNotNull(modelAndView.getModel().get("name"));
        assertEquals("greetings", modelAndView.getViewName());
      }
      
    }

    We created a mock LoginService and injected the mocked service to the controller in the setup method. In the case of an invalid login test, we stubbed the isValid method to return false and then asserted the error and view name. Similarly, in the case of the successful login test, we stubbed the isValid method to return true and subsequently asserted that no error message was set, and the greetings view was returned by the controller.

  11. Create a test for RegistrationController and mock RegistrationService. The following code snippet is the test:
      @RunWith(MockitoJUnitRunner.class)
      public class RegistrationControllerTest {
        @Mock
        private RegistrationService registrationService;
        private RegistrationController controller;
      
        @Before
        public void setup(){
          controller = new RegistrationController();
          controller.setRegistrationService(registrationService);
        }
      @Test
      public void when_invalid_user_id_geneartes_error_message() {
        when(registrationService.hasError()).thenReturn("error");
        ModelMap model = new ModelMap();
        ModelAndView modelAndView = controller.onRegistration(model);
        String message = (String) 
            modelAndView.getModel().get("message");
        assertNotNull(message);
        assertTrue(message.contains(RegistrationController.ERROR));
      }
      
      @Test
      public void when_valid_user_id_creates_user() throws Exception {
        when(registrationService.hasError()).thenReturn(null);
        ModelMap model = new ModelMap();
        ModelAndView modelAndView = controller.onRegistration(model);
        String message = (String) 
            modelAndView.getModel().get("message");
        assertNotNull(message);
        assertTrue(message.contains(RegistrationController.SUCCESS));
      }
    }

We unit tested the Spring controllers in isolation from the container. We didn't test the infrastructure, such as Spring annotations. In the next section, we'll unit test the services.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.75.70