Thursday, January 26, 2017

Spring Mock MVC and FileUpload Mocking

FileUpload Mocking using Spring Mock MVC
The aim of an Agile project is to deliver a minimum viable product to the client and based on client response we add the new functionality on that project.

Say, We have to implement a File Upload functionality, which uploads any types of files and extract metadata and content from file moreover it stores metadata into Solr/Elasticsearch for further intelligent searching.

According to agile mode, we first need to create a basic viable product.

So what is a minimum viable product in terms of  File upload functionality?

1.Create a UI which support File Upload
2. Implement a Middleware which extracts file content. (Not extract metadata)


So One UI developer starts developing the UI and You start developing the Middleware. But until UI and Middleware not finished we can’t do the Integration so should we wait for UI developer when he finishes then we start our Integration testing. No that is bad we waste our time for waiting for some other developer to finish his job.

Rather one thing we can do we Unit test our module by Mocking the UI as if files comes from UI.

In this Article, we will see How to Mock File Upload.

Here we use Spring Mock MVC for mocking MVC part
Use Junit and common File Upload.

Let create a pom.xml for resolving the dependencies

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.example</groupId>
 <artifactId>SpringWebExample</artifactId>
 <packaging>war</packaging>
 <version>0.0.1-SNAPSHOT</version>
 <name>SpringWebExample Maven Webapp</name>
 <url>http://maven.apache.org</url>
 <properties>
      <java-version>1.7</java-version>
      <org.springframework-version>4.3.0.RELEASE</org.springframework-version>
   </properties>
   <dependencies>
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-webmvc</artifactId>
          <version>${org.springframework-version}</version>
      </dependency>
      <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>javax.servlet-api</artifactId>
          <version>3.0.1</version>
          <scope>provided</scope>
      </dependency>
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-test</artifactId>
          <version>${org.springframework-version}</version>
      </dependency>
      <dependency>      
          <groupId>commons-fileupload</groupId>
          <artifactId>commons-fileupload</artifactId>
          <version>1.2</version>
      </dependency>
      <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>jstl</artifactId>
          <version>1.2</version>
      </dependency>
       <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>4.12</version>
         <scope>test</scope>
          </dependency>
   </dependencies>
   <build>
      <finalName>HelloWorld</finalName>
      <pluginManagement>
          <plugins>
              <plugin>
                  <groupId>org.apache.maven.plugins</groupId>
                  <artifactId>maven-compiler-plugin</artifactId>
                  <version>2.3.2</version>
                  <configuration>
                      <source>${java-version}</source>
                      <target>${java-version}</target>
                  </configuration>
              </plugin>
              <plugin>
                  <groupId>org.apache.maven.plugins</groupId>
                  <artifactId>maven-war-plugin</artifactId>
                  <version>2.4</version>
                  <configuration>
                      <warSourceDirectory>src/main/webapp</warSourceDirectory>
                      <warName>SpringWebExample</warName>
                      <failOnMissingWebXml>false</failOnMissingWebXml>
                  </configuration>
              </plugin>
          </plugins>
      </pluginManagement>
   </build>
</project>




Here we use Spring-webmvc,Spring-test,Common-fileupload,junit jars.


Now Create Spring configuration class

package com.example.anotatedconfiguration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.example")
public class SpringConfig extends WebMvcConfigurerAdapter{
   
   @Bean
   public ViewResolver viewResolver() {
      InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
      viewResolver.setViewClass(JstlView.class);
      viewResolver.setPrefix("/WEB-INF/pages/");
      viewResolver.setSuffix(".jsp");

      return viewResolver;
   }
   
   @Bean
    public CommonsMultipartResolver multipartResolver() {

       CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
       return commonsMultipartResolver;

    }

   @Override
   public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
      configurer.enable();
   }

}






Please note that here we register two Spring Beans

  1. ViewResolver
  2. Multipart Resolver


View Resolver helps to resolve the view.
Multipart Resolver helps to handle a Multipart request for File Upload.


In Next Step, we will create a Web configuration file which is use instead of web.xml because we follow annotation based spring MVC configuration.




package com.example.anotatedconfiguration;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

public class WebServletConfiguration implements WebApplicationInitializer{

   public void onStartup(ServletContext ctx) throws ServletException {
      AnnotationConfigWebApplicationContext webCtx = new AnnotationConfigWebApplicationContext();
      webCtx.register(SpringConfig.class);
      webCtx.setServletContext(ctx);

      ServletRegistration.Dynamic servlet = ctx.addServlet("dispatcher", new DispatcherServlet(webCtx));

      servlet.setLoadOnStartup(1);
      servlet.addMapping("/");
     
   }

}




Next, we will create a FileUpload Controller class

package com.example.controller;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.MultipartFile;

import com.example.model.FileInfo;

@Controller
public class FileUploadController {
   
   @RequestMapping(path= "/fileupload",method=RequestMethod.POST)
   public void uploadFiles(@ModelAttribute("files")FileInfo fileInfo,HttpServletRequest request) throws IOException{
     
      fileInfo.setName(request.getParameter("name"));
      fileInfo.setUplodedBy(request.getParameter("uploadedBy"));
      System.out.println(fileInfo);
      MultipartFile[] files = fileInfo.getFiles();
      for(MultipartFile file : files)
      {
          byte[] bytes = file.getBytes();
           Path path = Paths.get("/var/tmp/"+ file.getOriginalFilename());
           Files.write(path, bytes);
           System.out.println(file.getOriginalFilename() +  " :: File uploaded successFully");
      }
     
     
   }

}



Here I map the uploadFiles method with a file upload URL and tell Spring that it support post request.
Next I Bound the Files into  FileInfo files attribute using @ModelAttribute("files")FileInfo fileInfo annotation.

This tells Spring to bound Model attribute files in Fileinfo Object. SO if any value is bound under files key(Model Object) which is MutipartFile here then it will bound with FileInfo files attribute.


Next, we  fetch some information from Request and then Iterate over the files attribute to retrieve MultipartFile(We can add multiple files also ) and store them into /var/temp location on a Linux box.

FileInfo Model Class

package com.example.model;

import java.util.Arrays;

import org.springframework.web.multipart.MultipartFile;

public class FileInfo {
   
   public MultipartFile[] files;
   public String name;
   public String uplodedBy;
   public MultipartFile[] getFiles() {
      return files;
   }
   public void setFiles(MultipartFile[] files) {
      this.files = files;
   }
   public String getName() {
      return name;
   }
   public void setName(String name) {
      this.name = name;
   }
   public String getUplodedBy() {
      return uplodedBy;
   }
   public void setUplodedBy(String uplodedBy) {
      this.uplodedBy = uplodedBy;
   }
   @Override
   public String toString() {
      return "FileInfo [files=" + Arrays.toString(files) + ", name=" + name
              + ", uplodedBy=" + uplodedBy + "]";
   }
   
   
   

}




Now , our setup is complete so we test the solution using Spring Mock so without Integration with UI we can verify our FileUploadController works successfully or not.

package com.example.fileupload.test;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import com.example.anotatedconfiguration.SpringConfig;

@WebAppConfiguration
@ContextConfiguration(classes = SpringConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class FileUploadControllerTest {
   
   @Autowired
    private WebApplicationContext webApplicationContext;
   
   @Test
   public void testFileUpload() throws Exception
   {
      MockMultipartFile mockMultipartFile =
                 new MockMultipartFile("files", "FileUploadTest.txt", "text/plain", "This is a Test".getBytes());
     
      MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
         mockMvc.perform(MockMvcRequestBuilders.fileUpload("/fileupload")
                 .file(mockMultipartFile)
                 .param("name", "FileUploadTest.txt")
                 .param("uploadedBy","Shamik Mitra"));
       
         //Assert.assertTrue(true);
             
             
   }

}


Follow this class very carefully

Here I use two top-level annotations

@WebAppConfiguration
@ContextConfiguration(classes = SpringConfig.class)

It tells it a Web application and looks SpringConfig class for Spring beans.

Next, I create  MockMultipart file where I pass  name of file, Content-type and file content in the Constructor
 MockMultipartFile mockMultipartFile = new MockMultipartFile("files", "FileUploadTest.txt", "text/plain", "This is a Test".getBytes());


Next line I create a MockMVC object which acts as a Mock Spring MVC then I create a request by passing file, and some parameters into Mock Request.

mockMvc.perform(MockMvcRequestBuilders.fileUpload("/fileupload")
                 .file(mockMultipartFile)
                 .param("name", "FileUploadTest.txt")
                 .param("uploadedBy","Shamik Mitra"));






If we run the Junit

We see that a file named “FileUploadTest”  will create in /var/tmp folder and in console it prints

INFO: Looking for @ControllerAdvice: org.springframework.web.context.support.GenericWebApplicationContext@49ba4a47: startup date [Thu Jan 26 11:09:47 IST 2017]; root of context hierarchy
Jan 26, 2017 11:09:48 AM org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping registerHandlerMethod
INFO: Mapped "{[/fileupload],methods=[POST],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public void com.example.controller.FileUploadController.uploadFiles(com.example.model.FileInfo,javax.servlet.http.HttpServletRequest) throws java.io.IOException
Jan 26, 2017 11:09:48 AM org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping registerHandlerMethod
INFO: Mapped "{[/greet/{name}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String com.example.controller.GreetController.greet(java.lang.String,org.springframework.ui.ModelMap)
Jan 26, 2017 11:09:48 AM org.springframework.web.servlet.handler.SimpleUrlHandlerMapping registerHandler
INFO: Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler]
Jan 26, 2017 11:09:48 AM org.springframework.mock.web.MockServletContext log
INFO: Initializing Spring FrameworkServlet ''
Jan 26, 2017 11:09:48 AM org.springframework.test.web.servlet.TestDispatcherServlet initServletBean
INFO: FrameworkServlet '': initialization started
Jan 26, 2017 11:09:48 AM org.springframework.test.web.servlet.TestDispatcherServlet initServletBean
INFO: FrameworkServlet '': initialization completed in 28 ms
FileInfo [files=[org.springframework.mock.web.MockMultipartFile@547e6f03], name=FileUploadTest.txt, uplodedBy=Shamik Mitra]
FileUploadTest.txt :: File uploaded successFully
Jan 26, 2017 11:09:48 AM org.springframework.web.context.support.GenericWebApplicationContext doClose
INFO: Closing org.springframework.web.context.support.GenericWebApplicationContext@49ba4a47: startup date [Thu Jan 26 11:09:47 IST 2017]; root of context hierarchy