Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing Spring MultipartHttpServletRequest

Trying to test a spring controller that we have for multiple file upload. Here is the controller:

@RequestMapping("/vocabularys")
@Controller
public class VocabularyController {
...

The action I want to test:

@RequestMapping(value = "/import", method = {RequestMethod.PUT, RequestMethod.POST})
@ResponseBody
@CacheEvict(value="vocabulary", allEntries=true)
public Object importVocabulary(MultipartHttpServletRequest request, HttpServletResponse response) {
...

The resolver I have in the webmvc-config.xml:

<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver"/>

The code works just fine and all. I'm running into problems when I am trying to unit/integration test this.

Here is my attempt at the test:

public class VocabularyControllerTest extends BaseControllerTest {

    static final private String AdminUsername = "administrator";

    @Test
    public void shouldBeAbleToUploadAFile() throws Exception {
        createTestWorkspace();
        login(AdminUsername, "*");

        MockMultipartFile file = new MockMultipartFile("test_vocab.xml", new FileInputStream("src/test/files/acme_vocabulary.xml"));
        MockMultipartHttpServletRequestBuilder mockMultipartHttpServletRequestBuilder = (MockMultipartHttpServletRequestBuilder) fileUpload("/vocabularys/import").accept(MediaType.ALL).session(httpSession);
        mockMultipartHttpServletRequestBuilder.file(file);
        mockMultipartHttpServletRequestBuilder.content("whatever");

        ResultActions resultActions = mockMvc.perform(mockMultipartHttpServletRequestBuilder);

        resultActions.andExpect(status().isFound());
    }
}

Ignore the createWorkspace() and login() and stuff - those are for passing through some security filters.

The relevant part of the BaseControllerTest:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextHierarchy({
    @ContextConfiguration(locations = {
        "file:src/test/resources/META-INF/spring/applicationContext.xml",
        "file:src/test/resources/META-INF/spring/applicationContext-security.xml",
        "file:src/main/resources/META-INF/spring/applicationContext-database.xml",
        "file:src/main/resources/META-INF/spring/applicationContext-activiti.xml",
        "file:src/main/resources/META-INF/spring/applicationContext-cache.xml",
        "file:src/main/resources/META-INF/spring/applicationContext-jms.xml",
        "file:src/main/resources/META-INF/spring/applicationContext-mail.xml",
        "file:src/main/resources/META-INF/spring/applicationContext-mongo.xml"}),
    @ContextConfiguration(locations = {
        "file:src/main/webapp/WEB-INF/spring/webmvc-config.xml",
        "file:src/test/webapp/WEB-INF/spring/applicationContext-filters.xml"})
})
@Transactional
public class BaseControllerTest extends BaseTest {

    @Autowired
    WebApplicationContext wac;
    @Autowired
    MockHttpSession httpSession;
    @Autowired
    MockServletContext servletContext;
    @Autowired
    OpenEntityManagerInViewFilter openEntityManagerInViewFilter;
    @Autowired
    HiddenHttpMethodFilter hiddenHttpMethodFilter;
    @Autowired
    CharacterEncodingFilter characterEncodingFilter;
    @Autowired
    SessionFilter sessionFilter;
    @Autowired
    WorkflowAsSessionFilter workflowAsSessionFilter;
    @Autowired
    FilterChainProxy springSecurityFilterChain;
    @Autowired
    RequestFilter requestFilter;
    MockMvc mockMvc;

    protected static final String TestFileDir = "src/test/files/";

    @Before
    public void setUp() throws Exception {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
                .addFilter(openEntityManagerInViewFilter, "/*")
                .addFilter(hiddenHttpMethodFilter, "/*")
                .addFilter(characterEncodingFilter, "/*")
                .addFilter(sessionFilter, "/*")
                .addFilter(workflowAsSessionFilter, "/*")
                .addFilter(springSecurityFilterChain, "/*")
                .addFilter(requestFilter, "/*")
                .build();

        servletContext.setContextPath("/");

        Session session = Session.findBySessionId(httpSession.getId());
        if (session == null) {
            session = new Session();
            session.setJsessionid(httpSession.getId());
            session.persist();
        }
    }
...

The issue is that when I try debugging this, the perform action on the mockMvc object never hits my controller method. I thought it was an issue getting past our security filters (which is why I have all the login and stuff) but I tested other actions in the vocabulary controller and I am able to hit them just fine.

Thoughts? Ideas? Suggestions?

like image 476
Slania Avatar asked Feb 13 '26 22:02

Slania


2 Answers

Alright, found the issue.

Spring's MockMultipartHttpServletRequestBuilder returns a MockHttpMultipartServletRequest object eventually.

What the browser does however is post a multipart-encoded request which then gets picked up and parsed by the CommonsMultipartResolver bean defined in the XML.

In the test however, since we are already posting a MockHttpMultipartServletRequest, we don't want the resolver parsing this, so all we got to do is have a profile where the resolver doesn't kick in.

What we have chosen to do however is end up constructing a MockHttpServletRequest that has multipart encoding and put it through the Spring filters so that we can also integration test the resolver kicking in.

Unfortunately I don't see any support/helper in the Spring testing lib which allows you to take a MockHttpServletRequest and addPart() to it, or something to that effect => handcoded browser emulation function :(

like image 158
Slania Avatar answered Feb 16 '26 12:02

Slania


The simple way how to test multipart upload is use StandardServletMultipartResolver. and for test use this code:

    final MockPart profilePicture = new MockPart("profilePicture", "stview.jpg", "image/gif", "dsdsdsd".getBytes());
    final MockPart userData = new MockPart("userData", "userData", "application/json", "{\"name\":\"test aida\"}".getBytes());

    this.mockMvc.perform(
            fileUpload("/endUsers/" + usr.getId().toString()).with(new RequestPostProcessor() {

                @Override
                public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
                    request.addPart(profilePicture);
                    request.addPart(userData);
                    return request;
                }
            }) 

MockPart class

public class MockPart extends MockMultipartFile implements Part {

private Map<String, String> headers;

public MockPart(String name, byte[] content) {
    super(name, content);
    init();
}

public MockPart(String name, InputStream contentStream) throws IOException {
    super(name, contentStream);
    init();
}

public MockPart(String name, String originalFilename, String contentType, byte[] content) {
    super(name, originalFilename, contentType, content);
    init();
}

public MockPart(String name, String originalFilename, String contentType, InputStream contentStream) throws IOException {
    super(name, originalFilename, contentType, contentStream);
    init();
}

public void init() {
    this.headers = new HashMap<String, String>();
    if (getOriginalFilename() != null) {
        this.headers.put("Content-Disposition".toLowerCase(), "form-data; name=\"" + getName() + "\"; filename=\"" + getOriginalFilename() + "\"");
    } else {
        this.headers.put("Content-Disposition".toLowerCase(), "form-data; name=\"" + getName() + "\"");
    }
    if (getContentType() != null) {
        this.headers.put("Content-Type".toLowerCase(), getContentType());
    }
}

@Override
public void write(String fileName) throws IOException {
}

@Override
public void delete() throws IOException {
}

@Override
public String getHeader(String name) {
    return this.headers.get(name.toLowerCase());
}

@Override
public Collection<String> getHeaders(String name) {
    List<String> res = new ArrayList<String>();
    if (getHeader(name) != null) {
        res.add(getHeader(name));
    }
    return res;
}

@Override
public Collection<String> getHeaderNames() {
    return this.headers.keySet();
}

}

like image 33
David Ignjic Avatar answered Feb 16 '26 10:02

David Ignjic