Java Ninja Chronicles By Norris Shelton

Things I learned in the pursuit of code

Springframework MVC 3.2 introduced the ability to test controllers via MockMVC. In this example, we will test a controller that returns JSON via @ResponseBody.

Here are the dependencies that you will need.

            <!-- This is usually included as part of your normal dependencies.  If not, include this way -->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>3.1.0</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>${spring.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.mockito</groupId>
                <artifactId>mockito-core</artifactId>
                <version>1.9.5</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>com.jayway.jsonpath</groupId>
                <artifactId>json-path</artifactId>
                <version>${jsonpath.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>com.jayway.jsonpath</groupId>
                <artifactId>json-path-assert</artifactId>
                <version>${jsonpath.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.11</version>
                <scope>test</scope>
            </dependency>

This is an example of the spring context.

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans    http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/mvc      http://www.springframework.org/schema/mvc/spring-mvc.xsd
                           http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context.xsd">

    <mvc:annotation-driven/>

    <context:component-scan base-package="com.cdi.igs.core.spring.web"/>

</beans>

The basic test class needs to have the following annotations, properties and setup defined. Please note the static imports. You want to use these from the mock mvc request builders.

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
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.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;


@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration
public class TestGlobalControllerExceptionHandler {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }
//...
}

This is an example test method that calls a spring controller that returns a string. We are creating an empty model object that we convert to JSON. We tell the method that we are calling that we are sending JSON, because the method that we are calling expects to be sent a JSON object. We get validation errors in this case, that we compare with our expected results. The JSON response that we get back from the call to the controller is “{“fieldErrors”:[{“message”:”may not be null”},{“message”:”may not be null”}]}

    @Test
    public void testMyMethod() throws Exception {
        mockMvc.perform(post("/mymethod")
                        .content(new ObjectMapper().writeValueAsString(new TargetExceptionModel()))
                        .contentType(MediaType.APPLICATION_JSON))
               .andExpect(status().isBadRequest())
        .andExpect(jsonPath("$.fieldErrors", hasSize(2)))
        .andExpect(jsonPath("$.fieldErrors[*].message", contains("may not be null", "may not be null")))
        .andDo(print());
    }

Here is an example of a controller test method that users other types of matchers. Example data is:

{"status":"SUCCESS","message":null,"previousPage":null,"nextPage":"login/privacy_policy","errors":null,"exception":null,"idSetting":"pri","setting":"privacy-Starfish","value":"test","settingText":"lots and lots of text","date":"01-27-2014"}
    @Test
    public void testGetPrivacyByClientSuccess() throws Exception {
        mockMvc.perform(get("/privacy/pri"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.idSetting", containsString("pri")))
                .andExpect(jsonPath("$.setting", containsString("privacy-Starfish")))
                .andExpect(jsonPath("$.settingText", notNullValue()))
                .andExpect(jsonPath("$.value", containsString("test")))
                .andExpect(jsonPath("$.exception", nullValue()))
                .andDo(print());
    }

I needed to write a method that passed an object that contained an exception object back to the calling method. The data being sent back was:

{"status":"ERROR","message":null,"previousPage":null,"nextPage":null,"errors":null,"exception":{"cause":null,"stackTrace":[{"methodName":"copyProperties","fileName":"PropertyUtilsBean.java","lineNumber":276,"className":"org.apache.commons.beanutils.PropertyUtilsBean","nativeMethod":false},{"methodName":"copyProperties","fileName":"PropertyUtils.java","lineNumber":219,"className":"org.apache.commons.beanutils.PropertyUtils","nativeMethod":false},{"methodName":"getPrivacy","fileName":"LoginService.java","lineNumber":193,"className":"com.cdi.igs.core.login.LoginService","nativeMethod":false},{"methodName":"getPrivacyByClient","fileName":"LoginController.java","lineNumber":94,"className":"com.cdi.igs.services.LoginController","nativeMethod":false},{"methodName":"invoke0","fileName":"NativeMethodAccessorImpl.java","lineNumber":-2,"className":"sun.reflect.NativeMethodAccessorImpl","nativeMethod":true},{"methodName":"invoke","fileName":"NativeMethodAccessorImpl.java","lineNumber":57,"className":"sun.reflect.NativeMethodAccessorImpl","nativeMethod":false},{"methodName":"invoke","fileName":"DelegatingMethodAccessorImpl.java","lineNumber":43,"className":"sun.reflect.DelegatingMethodAccessorImpl","nativeMethod":false},{"methodName":"invoke","fileName":"Method.java","lineNumber":606,"className":"java.lang.reflect.Method","nativeMethod":false},{"methodName":"invoke","fileName":"InvocableHandlerMethod.java","lineNumber":214,"className":"org.springframework.web.method.support.InvocableHandlerMethod","nativeMethod":false},{"methodName":"invokeForRequest","fileName":"InvocableHandlerMethod.java","lineNumber":132,"className":"org.springframework.web.method.support.InvocableHandlerMethod","nativeMethod":false},{"methodName":"invokeAndHandle","fileName":"ServletInvocableHandlerMethod.java","lineNumber":104,"className":"org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod","nativeMethod":false},{"methodName":"invokeHandleMethod","fileName":"RequestMappingHandlerAdapter.java","lineNumber":749,"className":"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter","nativeMethod":false},{"methodName":"handleInternal","fileName":"RequestMappingHandlerAdapter.java","lineNumber":690,"className":"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter","nativeMethod":false},{"methodName":"handle","fileName":"AbstractHandlerMethodAdapter.java","lineNumber":83,"className":"org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter","nativeMethod":false},{"methodName":"doDispatch","fileName":"DispatcherServlet.java","lineNumber":945,"className":"org.springframework.web.servlet.DispatcherServlet","nativeMethod":false},{"methodName":"doService","fileName":"DispatcherServlet.java","lineNumber":876,"className":"org.springframework.web.servlet.DispatcherServlet","nativeMethod":false},{"methodName":"processRequest","fileName":"FrameworkServlet.java","lineNumber":961,"className":"org.springframework.web.servlet.FrameworkServlet","nativeMethod":false},{"methodName":"doGet","fileName":"FrameworkServlet.java","lineNumber":852,"className":"org.springframework.web.servlet.FrameworkServlet","nativeMethod":false},{"methodName":"service","fileName":"HttpServlet.java","lineNumber":687,"className":"javax.servlet.http.HttpServlet","nativeMethod":false},{"methodName":"service","fileName":"FrameworkServlet.java","lineNumber":837,"className":"org.springframework.web.servlet.FrameworkServlet","nativeMethod":false},{"methodName":"service","fileName":"TestDispatcherServlet.java","lineNumber":64,"className":"org.springframework.test.web.servlet.TestDispatcherServlet","nativeMethod":false},{"methodName":"service","fileName":"HttpServlet.java","lineNumber":790,"className":"javax.servlet.http.HttpServlet","nativeMethod":false},{"methodName":"doFilter","fileName":"MockFilterChain.java","lineNumber":170,"className":"org.springframework.mock.web.MockFilterChain$ServletFilterProxy","nativeMethod":false},{"methodName":"doFilter","fileName":"MockFilterChain.java","lineNumber":137,"className":"org.springframework.mock.web.MockFilterChain","nativeMethod":false},{"methodName":"perform","fileName":"MockMvc.java","lineNumber":141,"className":"org.springframework.test.web.servlet.MockMvc","nativeMethod":false},{"methodName":"testGetPrivacyByClientFail","fileName":"TestLoginController.java","lineNumber":96,"className":"com.cdi.igs.services.TestLoginController","nativeMethod":false},{"methodName":"invoke0","fileName":"NativeMethodAccessorImpl.java","lineNumber":-2,"className":"sun.reflect.NativeMethodAccessorImpl","nativeMethod":true},{"methodName":"invoke","fileName":"NativeMethodAccessorImpl.java","lineNumber":57,"className":"sun.reflect.NativeMethodAccessorImpl","nativeMethod":false},{"methodName":"invoke","fileName":"DelegatingMethodAccessorImpl.java","lineNumber":43,"className":"sun.reflect.DelegatingMethodAccessorImpl","nativeMethod":false},{"methodName":"invoke","fileName":"Method.java","lineNumber":606,"className":"java.lang.reflect.Method","nativeMethod":false},{"methodName":"runReflectiveCall","fileName":"FrameworkMethod.java","lineNumber":47,"className":"org.junit.runners.model.FrameworkMethod$1","nativeMethod":false},{"methodName":"run","fileName":"ReflectiveCallable.java","lineNumber":12,"className":"org.junit.internal.runners.model.ReflectiveCallable","nativeMethod":false},{"methodName":"invokeExplosively","fileName":"FrameworkMethod.java","lineNumber":44,"className":"org.junit.runners.model.FrameworkMethod","nativeMethod":false},{"methodName":"evaluate","fileName":"InvokeMethod.java","lineNumber":17,"className":"org.junit.internal.runners.statements.InvokeMethod","nativeMethod":false},{"methodName":"evaluate","fileName":"RunBefores.java","lineNumber":26,"className":"org.junit.internal.runners.statements.RunBefores","nativeMethod":false},{"methodName":"evaluate","fileName":"RunBeforeTestMethodCallbacks.java","lineNumber":74,"className":"org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks","nativeMethod":false},{"methodName":"evaluate","fileName":"RunAfterTestMethodCallbacks.java","lineNumber":83,"className":"org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks","nativeMethod":false},{"methodName":"evaluate","fileName":"SpringRepeat.java","lineNumber":72,"className":"org.springframework.test.context.junit4.statements.SpringRepeat","nativeMethod":false},{"methodName":"runChild","fileName":"SpringJUnit4ClassRunner.java","lineNumber":232,"className":"org.springframework.test.context.junit4.SpringJUnit4ClassRunner","nativeMethod":false},{"methodName":"runChild","fileName":"SpringJUnit4ClassRunner.java","lineNumber":89,"className":"org.springframework.test.context.junit4.SpringJUnit4ClassRunner","nativeMethod":false},{"methodName":"run","fileName":"ParentRunner.java","lineNumber":238,"className":"org.junit.runners.ParentRunner$3","nativeMethod":false},{"methodName":"schedule","fileName":"ParentRunner.java","lineNumber":63,"className":"org.junit.runners.ParentRunner$1","nativeMethod":false},{"methodName":"runChildren","fileName":"ParentRunner.java","lineNumber":236,"className":"org.junit.runners.ParentRunner","nativeMethod":false},{"methodName":"access$000","fileName":"ParentRunner.java","lineNumber":53,"className":"org.junit.runners.ParentRunner","nativeMethod":false},{"methodName":"evaluate","fileName":"ParentRunner.java","lineNumber":229,"className":"org.junit.runners.ParentRunner$2","nativeMethod":false},{"methodName":"evaluate","fileName":"RunBeforeTestClassCallbacks.java","lineNumber":61,"className":"org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks","nativeMethod":false},{"methodName":"evaluate","fileName":"RunAfterTestClassCallbacks.java","lineNumber":71,"className":"org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks","nativeMethod":false},{"methodName":"run","fileName":"ParentRunner.java","lineNumber":309,"className":"org.junit.runners.ParentRunner","nativeMethod":false},{"methodName":"run","fileName":"SpringJUnit4ClassRunner.java","lineNumber":175,"className":"org.springframework.test.context.junit4.SpringJUnit4ClassRunner","nativeMethod":false},{"methodName":"run","fileName":"JUnitCore.java","lineNumber":160,"className":"org.junit.runner.JUnitCore","nativeMethod":false},{"methodName":"startRunnerWithArgs","fileName":"JUnit4IdeaTestRunner.java","lineNumber":74,"className":"com.intellij.junit4.JUnit4IdeaTestRunner","nativeMethod":false},{"methodName":"prepareStreamsAndStart","fileName":"JUnitStarter.java","lineNumber":211,"className":"com.intellij.rt.execution.junit.JUnitStarter","nativeMethod":false},{"methodName":"main","fileName":"JUnitStarter.java","lineNumber":67,"className":"com.intellij.rt.execution.junit.JUnitStarter","nativeMethod":false},{"methodName":"invoke0","fileName":"NativeMethodAccessorImpl.java","lineNumber":-2,"className":"sun.reflect.NativeMethodAccessorImpl","nativeMethod":true},{"methodName":"invoke","fileName":"NativeMethodAccessorImpl.java","lineNumber":57,"className":"sun.reflect.NativeMethodAccessorImpl","nativeMethod":false},{"methodName":"invoke","fileName":"DelegatingMethodAccessorImpl.java","lineNumber":43,"className":"sun.reflect.DelegatingMethodAccessorImpl","nativeMethod":false},{"methodName":"invoke","fileName":"Method.java","lineNumber":606,"className":"java.lang.reflect.Method","nativeMethod":false},{"methodName":"main","fileName":"AppMain.java","lineNumber":120,"className":"com.intellij.rt.execution.application.AppMain","nativeMethod":false}],"message":"No origin bean specified","localizedMessage":"No origin bean specified","suppressed":[]},"idSetting":"Starfish1","setting":null,"value":null,"settingText":null,"date":null}
    @Test
    public void testGetPrivacyByClientFail() throws Exception {
        mockMvc.perform(get("/privacy/Starfish1"))
               .andExpect(status().isOk())
               .andExpect(jsonPath("$.idSetting", containsString("Starfish1")))
               .andExpect(jsonPath("$.setting", nullValue()))
               .andExpect(jsonPath("$.settingText", nullValue()))
               .andExpect(jsonPath("$.value", nullValue()))
               .andExpect(jsonPath("$.exception.cause", nullValue()))
               .andExpect(jsonPath("$.exception.stackTrace", notNullValue()))
               .andExpect(jsonPath("$.exception.stackTrace[0].methodName", notNullValue()))
               .andDo(print());
    }

To get the content and store it in a variable outside the test, add .andReturn() to the end of the perform and store the result in a MvcResult, then get the content.

        MvcResult result = mockMvc.perform(post("/login")
                                               .content(
                                                   "{"userName":"screen011","password":"Jason3080!"," +
                                                   ""remoteAddress":"0.0" +
                                                   ".0.0"}"
                                                       )
                                               .contentType(MediaType.APPLICATION_JSON))
               .andExpect(status().isOk())
               .andExpect(jsonPath("$.exception", nullValue()))
               .andExpect(jsonPath("$.errors", hasSize(0)))
               .andExpect((jsonPath("$.ssoToken", notNullValue())))
               .andDo(print())
               .andReturn();
        String content = result.getResponse().getContentAsString();

Then if you need to extract a value out of the JSON, you can use JsonPath out of jayway like

        String content = result.getResponse().getContentAsString();

        assertNotNull(content);
        String ssoToken = JsonPath.read(content, "$.ssoToken");
        assertNotNull(ssoToken);

February 27th, 2014

Posted In: Java, MockMVC, Spring, Spring MVC

Tags: , , , , , , ,

2 Comments

Normally, I have been able to generate a Soap client and be on my way. This particular time I had to perform some business logic on the client. One of the first things that I encountered was the nuisance of debugging classes without a toString() implementation. It’s no biggie, but it does waste time when you have to open a class to inspect it’s contents. When it has a toString() implementation, your editor will generally use this to display the class. Making one less step that I have to perform.

The business logic I had to implement involved splitting a list of items into two groups, according to the value of one of the properties. In addition, if the object was already in the list, but had a different address, I had to add the current address to the existing object instead of adding it again. Great. Now I need to be able to identify an object in a list. I found that JAXB-2 Basics added implementations of equals() and hashcode() methods.

Just when I thought I was done, I had to add the serializable interface because the web application is marked as distributable. This wasn’t provided by JAXB2-Basics and I had to use an XML binding to add that.

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.appriss.jxportal</groupId>
        <artifactId>jxportal</artifactId>
        <version>47.0-SNAPSHOT</version>
    </parent>

    <artifactId>tlo-client</artifactId>
    <name>tlo-client</name>

    <properties>
        <cxf.version>2.5.2</cxf.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxws</artifactId>
            <version>${cxf.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http</artifactId>
            <version>${cxf.version}</version>
        </dependency>
        <!-- Jetty is needed if you're are not using the CXFServlet -->
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http-jetty</artifactId>
            <version>${cxf.version}</version>
        </dependency>

        <!--
            Spring dependencies - needed to force a newer version than CXF used
        -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.jvnet.jaxb2_commons</groupId>
            <artifactId>jaxb2-basics-runtime</artifactId>
            <version>0.6.3</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.cxf</groupId>
                <artifactId>cxf-codegen-plugin</artifactId>
                <version>${cxf.version}</version>
                <executions>
                    <execution>
                        <id>generate-sources</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>wsdl2java</goal>
                        </goals>
                        <configuration>
                            <sourceRoot>${basedir}/target/generated-sources/cxf</sourceRoot>
                            <wsdlOptions>
                                <wsdlOption>
                                    <!--note: Started getting errors by having this hit their URL directly-->
                                    <wsdl>https://webservice.tlo.com/TLOWebService.asmx?wsdl</wsdl>
                                    <!--<wsdl>${basedir}/src/main/resources/TLOWebService-20120306.wsdl</wsdl>-->
                                    <bindingFiles>
                                        <bindingFile>${basedir}/src/main/resources/binding.xjb</bindingFile>
                                    </bindingFiles>
                                    <extraargs>
                                        <extraarg>-xjc-XtoString</extraarg>
                                        <extraarg>-xjc-Xequals</extraarg>
                                        <extraarg>-xjc-XhashCode</extraarg>
                                    </extraargs>
                                </wsdlOption>
                            </wsdlOptions>
                        </configuration>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>org.jvnet.jaxb2_commons</groupId>
                        <artifactId>jaxb2-basics</artifactId>
                        <version>0.6.3</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
</project>

To use JAXB2-Basics, I added the module dependency starting at line 52. I also added a dependency for the cxf-codegen-plugin at line 93. After that, I had to add the at line 83.

  • -xjc-XtoString – triggers the generation of the toString() method
  • -xjc-Xequals – triggers the generation of the equals() method
  • -xjc-XhashCode – triggers the generation of the hashcode() method

The jaxb binding took a bit more work, but was fairly easy also. I specified the binding file on line 80. ${basedir} is a maven variable that is set to the directory of the module. The binding file is fairly straight forward. I specified a global binding that said serializable. I also specified the optional uid. If the uid is specified, then the value will be specified in the classes. NOTE: that the same uid value will be specified for all classes.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<jaxb:bindings version="2.0" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb">
    <jaxb:bindings>
        <jaxb:globalBindings>
            <jaxb:serializable uid="1"/>
        </jaxb:globalBindings>
    </jaxb:bindings>
</jaxb:bindings>

April 3rd, 2012

Posted In: Java, Maven, xml

Tags: , ,

Leave a Comment

LinkedIn Auto Publish Powered By : XYZScripts.com