3

I have built a working Spring MVC web app utilising Hibernate and JPA with a single end point that returns JSON only successfully when called through a web browser.

I have also got passing unit tests against classes, in particular the main controller that maps the end point.

So my next step was to write some behavioural tests using the spring MVC test framework to test the JSON endpoint with all its working parts (config, controller, serivice, model and repository).

I have successfully set up a test that uses my application configuration, injects/autowires things together, then makes a 'GET' request against my end point. I can debug the calls, following things through from hitting the controller end point, through the service, into the repository, successfully getting data and returning the correct model object. However, the controller then returns a 406 not accepted response and the test fails.

I have spent 2 days working through this issue and have read just about every blog post and SO answer I can take with no suggestions working.

Here is my Controller:

@Controller
public class DataController {

    @Autowired
    private IService service;

    @RequestMapping(
            value = "/data",
            method = RequestMethod.GET,
            produces = "application/json"
    )
    public @ResponseBody
    ModelData getDataByCode(@RequestParam String code) {
        return service.getDataCode(code);
    }

}

Service:

@Service("dataService")
public class DataService implements IService {

    @Autowired
    private IDataRepository dataRepository;

    @Transactional
    public ModelData getDataByCode(String code){
        return dataRepository.getDataByCode(code);
    }
}

Repository:

@Repository("dataRepository")
public class DataRepository implements IDataRepository {

    @PersistenceContext
    private EntityManager entityManager;

    public ModelData getDataByCode(String code) throws IllegalStateException, PersistenceException {
        String selectJPAQueryBase = "Select data from ModelData data where data.code = %s";
        Query query = entityManager.createQuery(String.format(selectJPAQueryBase, code));
        return (ModelData) query.getSingleResult();
    }

}

Dependencies relevant to the core features:

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>3.2.14.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.6.2</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.6.2</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.6.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-oxm</artifactId>
            <version>3.2.14.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>4.2.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.0.1.Final</version>
        </dependency>
        <dependency>
            <groupId>javax.transaction</groupId>
            <artifactId>jta</artifactId>
            <version>1.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>3.2.14.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>3.2.14.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>net.sourceforge.jtds</groupId>
            <artifactId>jtds</artifactId>
            <version>1.3.1</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-java</artifactId>
            <version>1.2.4</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-junit</artifactId>
            <version>1.2.4</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-all</artifactId>
            <version>1.3</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>3.2.14.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.jayway.jsonpath</groupId>
            <artifactId>json-path</artifactId>
            <version>0.8.1</version>
            <scope>test</scope>
        </dependency>

JPA context:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">

    <context:annotation-config />

    <!-- where spring will start to look for classes that are annotated, allow this to find all annotated classes -->
    <context:component-scan base-package="com.app"/>

    <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="punit"/>
        <property name="dataSource" ref="dataSource"/>
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="showSql" value="true"/>
            </bean>
        </property>
        <property name="jpaPropertyMap">
            <map>
                <entry key="hibernate.dialect" value="org.hibernate.dialect.SQLServer2008Dialect"/>
                <entry key="hibernate.hbm2ddl.auto" value="validate"/>
                <entry key="hibernate.format_sql" value="true"/>
            </map>
        </property>
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>

    <tx:annotation-driven transaction-manager="transactionManager"/>

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="net.sourceforge.jtds.jdbc.Driver"/>
        <property name="url" value="jdbc:jtds:sqlserver://0.0.0.0:1433/dbname"/>
        <property name="username" value="username"/>
        <property name="password" value="password"/>
    </bean>

</beans>

Web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:/jpaContext.xml</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>dataByCode</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/config/servlet-config.xml</param-value>
        </init-param>
    </servlet>

    <servlet-mapping>
        <servlet-name>dataByCode</servlet-name>
        <url-pattern>/data</url-pattern>
    </servlet-mapping>

</web-app>

Servlet config:

<?xml version="1.0" encoding="UTF-8"?>
<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/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">

    <!-- allows POJO objects to just use annotations for simply hooking into spring framework -->
    <mvc:annotation-driven/>

    <!-- where spring will start to look annotated classes, keep this to controller level -->
    <context:component-scan base-package="com.app"/>

    <!-- load properties files and similar -->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="properties"/>
    </bean>

    <!-- basic resolver to handle jsp pages, usually linked to returning html -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
        <property name="order" value="2"/>
    </bean>

</beans>

The failing test:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("classpath:jpaContext.xml")
public class GetDataCodeIntTest {

    @Autowired
    private WebApplicationContext webApplicationContext;

    private MockMvc mockMvc;

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

    @Test
    public void getDataByCode() throws Exception {
        String code = "1234";
        this.mockMvc.perform(MockMvcRequestBuilders.get("/data").param("code", code))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath("$.code").value(code));
    }

}

Key thing here is that the application works perfectly through the browser, but fails using the spring mvc test framework at the point where the controller returns a response.


UPDATE:

The solution was to inject my servlet-config.xml along with the jpaContext into the @ContextConfiguration, the test ran perfectly after that.

Interestingly I couldn't just load the servlet-config.xml from the classpath however and I didnt want a test specific version to maintain, so I moved it into my resources folder. Whether that was a good idea I'm undecided, but it works from the classpath now.

Jeremy
  • 3,418
  • 2
  • 32
  • 42
  • Try to set @Consumes if you are setting accept also check the dependencies [as here described](https://stackoverflow.com/questions/26615416/406-spring-mvc-json-not-acceptable-according-to-the-request-accept-headers) – ernestk Dec 07 '15 at 10:41
  • Where should I put ```@Consumes``` please? – Jeremy Dec 07 '15 at 10:48
  • I changed the versions as per the linked answer with no luck, still 406 in the test. Plus to be honest the code all works with the current dependencies, it just fails with the spring mvc test framework test. – Jeremy Dec 07 '15 at 10:51
  • sorry, this was bad suggestion – ernestk Dec 07 '15 at 14:33

3 Answers3

1

Try adding your servletConfig.xml to the @ContextConfiguration on your test class:

@ContextConfiguration({"classpath:jpaContext.xml", "classpath:path/to/servlet-context.xml"})

The important bit in there is <mvc:annotation-driven/>.

Also note that you can simplify your controller a bit, and make its purpose clearer, by annotating it @RestController instead of @Controller.

whistling_marmot
  • 3,561
  • 3
  • 25
  • 39
1

Try to add your spring-context.xml for testing via @ContextConfiguration (ex: @ContextConfiguration("classpath:spring-context.xml")), tried your code with this option, it worked.

ernestk
  • 72
  • 6
0

I think your issue is your misunderstand your "Content-type" header and the result produces by your REST Controller.

In fact, you put

MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)

means you're sending JSON to your controller. Whereas, your controller is trying to receive param in your request not JSON:

@RequestParam String code

Probably the reason why you have this 406 (which usually means a default in header of your request).

Julien Alary
  • 780
  • 1
  • 5
  • 16
  • It fails before that at ```.andExpect(MockMvcResultMatchers.status().isOk())```. Plus I have tried with and without specifying content type in the test and it still fails with a 406. – Jeremy Dec 07 '15 at 10:42
  • I'm pretty sure that ```.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON))``` checks that the data sent back from the controller is of type JSON, which is exactly what I want to check. or at least that the header in the return request has a content type of 'application/json' anyway. – Jeremy Dec 07 '15 at 10:45
  • Although he points at the result I guess he means the accept header and not the check at the result. Remove the `accept` part as that isn't what the browser would send (unless you use JS and explicitly set the content-type yourself). – M. Deinum Dec 07 '15 at 10:47
  • Yes I agree, that was in there only because I was experimenting with specifically setting the header content type for the request. i have removed it and the same result, 406 test fails. I'll adjust the detail to reflect that. – Jeremy Dec 07 '15 at 10:54