Search code examples
springjunitmockitoresttemplate

Write Unit Test for API endpoint Controller


I have a spring controller which responds to API requests. I am trying to write Unit Tests for the controller class methods. I'll expose the controller code below:
AuditRestController.java

package com.audittrail.auditTrail.controller;

import com.audittrail.auditTrail.services.CreateAuditService;
import com.audittrail.auditTrail.services.GetAuditService;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.*;
import java.io.IOException;

@Controller("audit")
public class AuditRestController {
    @Autowired
    private GetAuditService getAuditService;
    @Autowired
    CreateAuditService createAuditService;

    @JsonIgnoreProperties(ignoreUnknown = true)
    @RequestMapping(value = "/audit/checkevent", method = RequestMethod.POST, consumes = {
            MediaType.APPLICATION_JSON_VALUE })
    @ResponseBody
    public String create(@Validated @RequestBody String audi) throws IOException {
        return createAuditService.createEvent(audi);
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    @RequestMapping(value = "/audit/getevent", method = RequestMethod.POST, consumes = {
            MediaType.APPLICATION_JSON_VALUE })
    @ResponseBody
    public String getObjectId(@Validated @RequestBody String audi) throws IOException {
        return getAuditService.getEvent(audi);
    }
}

One of the services that I am using is getAuditService.
GetAuditRestController.java

package com.audittrail.auditTrail.services;

import static org.springframework.http.MediaType.APPLICATION_JSON;

import java.io.IOException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import com.emc.leap.commons.client.web.LeapRestTemplate;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

@Service
public class GetAuditService {
    @Autowired
    private final RestTemplate clientTemplate;

    public GetAuditService(@Qualifier(LeapRestTemplate.LEAP_CLIENT_REST_TEMPLATE) RestTemplate clientTemplate) {

        this.clientTemplate = clientTemplate;
    }

    public String getEvent(String audi) throws JsonProcessingException, IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode jsonNode = null;

        jsonNode = objectMapper.readTree(audi);

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<String>(headers);

        String auditGetRequestURL = "some external url";
        ResponseEntity<String> auditServiceGetRequest;
        auditServiceGetRequest = this.clientTemplate.exchange(auditGetRequestURL, HttpMethod.GET, entity, String.class);
        return auditServiceGetRequest.getBody().toString();
    }
}

One thing to note is that this entire project does not have a main class. I am trying to build a non executable jar to be used in some other project. I have checked the jar without unit test cases and it is working fine. But I am also trying to write unit test cases. This is bit of code that I am trying to execute.
AuditRestControllerTest.java

package com.audittrail.audittrail;

import com.google.gson.JsonObject;

import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

import com.audittrail.auditTrail.controller.AuditObject;
import com.audittrail.auditTrail.controller.AuditRestController;
import com.audittrail.auditTrail.services.CreateAuditService;
import com.audittrail.auditTrail.services.GetAuditService;

@RunWith(SpringJUnit4ClassRunner.class)
@WebMvcTest(AuditRestController.class)
public class AuditRestControllerTest {

    @Autowired
    private MockMvc mockMvc;
    @Autowired
    AuditRestController auditRestController;

    @MockBean
    GetAuditService getAuditService;
    @MockBean
    CreateAuditService CreateAuditService;
    @MockBean
    AuditObject auditObject;

//  @Before
//  public void setUp() {
//      mockMvc = MockMvcBuilders.standaloneSetup(new AuditRestController()).build();
//      System.out.println("setup done");
//  }

    @Test
    public void checkObjectId() throws Exception {
        JsonObject demoInput = new JsonObject();
        demoInput.addProperty("id", "1");
        demoInput.addProperty("name", "Rajesh");
        JsonObject demoResponse = new JsonObject();
        demoResponse.addProperty("eventName", "Update");
        demoResponse.addProperty("ObjectId", "12344");
        Mockito.when(getAuditService.getEvent(demoInput.toString())).thenReturn(demoResponse.toString());
        mockMvc.perform(MockMvcRequestBuilders.post("/audit/getevent").contentType(MediaType.APPLICATION_JSON)
                .content(demoInput.toString()))
                .andExpect(MockMvcResultMatchers.jsonPath("$.eventNmae", Matchers.is("Update")));
    }
}

Shown Below is the pom.xml file that I have:
Pom.xml

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.auditTrail</groupId>
    <artifactId>audit-trail-test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>audit-trail</name>
    <description>Audit Service Integration</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.6.2</version>
        </dependency>
        <dependency>
            <groupId>com.emc.leap</groupId>
            <artifactId>leap-commons-client</artifactId>
            <version>1.1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.emc.leap</groupId>
            <artifactId>leap-commons-core</artifactId>
            <version>1.1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-all</artifactId>
            <version>1.9.5</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>2.1.1.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>

    </dependencies>
</project>

And finally here is the dependency tree for my maven:

[INFO] com.auditTrail:audit-trail-test:jar:0.0.1-SNAPSHOT
[INFO] +- com.google.code.gson:gson:jar:2.6.2:compile
[INFO] +- com.emc.leap:leap-commons-client:jar:1.1.0-SNAPSHOT:compile
[INFO] |  +- org.springframework:spring-context:jar:4.3.17.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-aop:jar:4.3.17.RELEASE:compile
[INFO] |  |  \- org.springframework:spring-expression:jar:4.3.17.RELEASE:compile
[INFO] |  +- org.apache.httpcomponents:httpclient:jar:4.5.1:compile
[INFO] |  |  +- org.apache.httpcomponents:httpcore:jar:4.4.3:compile
[INFO] |  |  +- commons-logging:commons-logging:jar:1.2:compile
[INFO] |  |  \- commons-codec:commons-codec:jar:1.9:compile
[INFO] |  +- org.springframework:spring-web:jar:4.3.17.RELEASE:compile
[INFO] |  +- org.apache.commons:commons-lang3:jar:3.5:compile
[INFO] |  +- org.slf4j:slf4j-api:jar:1.7.21:compile
[INFO] |  \- com.eatthepath:fast-uuid:jar:0.1:compile
[INFO] +- com.emc.leap:leap-commons-core:jar:1.1.0-SNAPSHOT:compile
[INFO] |  +- com.emc.dctm:security:jar:1.0.9:compile
[INFO] |  +- com.fasterxml.jackson.core:jackson-core:jar:2.7.5:compile
[INFO] |  +- com.fasterxml.jackson.core:jackson-annotations:jar:2.7.5:compile
[INFO] |  +- com.fasterxml.jackson.core:jackson-databind:jar:2.7.5:compile
[INFO] |  +- commons-io:commons-io:jar:2.4:compile
[INFO] |  +- org.springframework.security:spring-security-jwt:jar:1.0.9.RELEASE:compile
[INFO] |  |  \- org.bouncycastle:bcpkix-jdk15on:jar:1.56:compile
[INFO] |  |     \- org.bouncycastle:bcprov-jdk15on:jar:1.56:compile
[INFO] |  +- org.springframework.security:spring-security-core:jar:4.2.6.RELEASE:compile
[INFO] |  |  \- aopalliance:aopalliance:jar:1.0:compile
[INFO] |  +- com.google.guava:guava:jar:18.0:compile
[INFO] |  \- net.jodah:expiringmap:jar:0.3.1:compile
[INFO] +- javax.servlet:javax.servlet-api:jar:3.1.0:provided
[INFO] +- org.mockito:mockito-all:jar:1.9.5:test
[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:2.1.1.RELEASE:test
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:2.1.1.RELEASE:test
[INFO] |  |  +- org.springframework.boot:spring-boot:jar:2.1.1.RELEASE:test
[INFO] |  |  +- org.springframework.boot:spring-boot-autoconfigure:jar:2.1.1.RELEASE:test
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:2.1.1.RELEASE:test
[INFO] |  |  |  +- ch.qos.logback:logback-classic:jar:1.2.3:test
[INFO] |  |  |  |  \- ch.qos.logback:logback-core:jar:1.2.3:test
[INFO] |  |  |  +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.11.1:test
[INFO] |  |  |  |  \- org.apache.logging.log4j:log4j-api:jar:2.11.1:test
[INFO] |  |  |  \- org.slf4j:jul-to-slf4j:jar:1.7.25:test
[INFO] |  |  +- javax.annotation:javax.annotation-api:jar:1.3.2:test
[INFO] |  |  \- org.yaml:snakeyaml:jar:1.23:test
[INFO] |  +- org.springframework.boot:spring-boot-test:jar:2.1.1.RELEASE:test
[INFO] |  +- org.springframework.boot:spring-boot-test-autoconfigure:jar:2.1.1.RELEASE:test
[INFO] |  +- com.jayway.jsonpath:json-path:jar:2.4.0:test
[INFO] |  |  \- net.minidev:json-smart:jar:2.3:test
[INFO] |  |     \- net.minidev:accessors-smart:jar:1.2:test
[INFO] |  |        \- org.ow2.asm:asm:jar:5.0.4:test
[INFO] |  +- junit:junit:jar:4.12:test
[INFO] |  +- org.assertj:assertj-core:jar:3.11.1:test
[INFO] |  +- org.mockito:mockito-core:jar:2.23.4:test
[INFO] |  |  +- net.bytebuddy:byte-buddy:jar:1.9.3:test
[INFO] |  |  +- net.bytebuddy:byte-buddy-agent:jar:1.9.3:test
[INFO] |  |  \- org.objenesis:objenesis:jar:2.6:test
[INFO] |  +- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] |  +- org.hamcrest:hamcrest-library:jar:1.3:test
[INFO] |  +- org.skyscreamer:jsonassert:jar:1.5.0:test
[INFO] |  |  \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test
[INFO] |  +- org.springframework:spring-core:jar:5.1.3.RELEASE:compile
[INFO] |  |  \- org.springframework:spring-jcl:jar:5.1.3.RELEASE:compile
[INFO] |  +- org.springframework:spring-test:jar:5.1.3.RELEASE:test
[INFO] |  \- org.xmlunit:xmlunit-core:jar:2.6.2:test
[INFO] \- org.springframework:spring-tx:jar:5.1.5.RELEASE:compile
[INFO]    \- org.springframework:spring-beans:jar:5.1.5.RELEASE:compile

And Now finally here is the error that I am getting after mvn:install

-<testcase time="0.017" name="initializationError" classname="com.audittrail.audittrail.AuditRestControllerTest">

<error type="java.lang.NoClassDefFoundError" message="org/springframework/web/servlet/config/annotation/WebMvcConfigurer">java.lang.NoClassDefFoundError: org/springframework/web/servlet/config/annotation/WebMvcConfigurerat org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTypeExcludeFilter.<clinit>(WebMvcTypeExcludeFilter.java:58)at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)at java.lang.reflect.Constructor.newInstance(Unknown Source)at org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer.instantiateTypeExcludeFilter(TypeExcludeFiltersContextCustomizer.java:66)at org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer.instantiateTypeExcludeFilters(TypeExcludeFiltersContextCustomizer.java:55)at org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer.<init>(TypeExcludeFiltersContextCustomizer.java:48)at org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizerFactory.createContextCustomizer(TypeExcludeFiltersContextCustomizerFactory.java:47)at org.springframework.test.context.support.AbstractTestContextBootstrapper.getContextCustomizers(AbstractTestContextBootstrapper.java:404)at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildMergedContextConfiguration(AbstractTestContextBootstrapper.java:376)at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildDefaultMergedContextConfiguration(AbstractTestContextBootstrapper.java:312)at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildMergedContextConfiguration(AbstractTestContextBootstrapper.java:265)at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildTestContext(AbstractTestContextBootstrapper.java:108)at org.springframework.boot.test.context.SpringBootTestContextBootstrapper.buildTestContext(SpringBootTestContextBootstrapper.java:99)at org.springframework.test.context.TestContextManager.<init>(TestContextManager.java:139)at org.springframework.test.context.TestContextManager.<init>(TestContextManager.java:124)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTestContextManager(SpringJUnit4ClassRunner.java:151)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.<init>(SpringJUnit4ClassRunner.java:142)at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)at java.lang.reflect.Constructor.newInstance(Unknown Source)at org.junit.internal.builders.AnnotatedBuilder.buildRunner(AnnotatedBuilder.java:104)at org.junit.internal.builders.AnnotatedBuilder.runnerForClass(AnnotatedBuilder.java:86)at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:26)at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)at org.junit.internal.requests.ClassRequest.getRunner(ClassRequest.java:33)at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:250)at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)at java.lang.reflect.Method.invoke(Unknown Source)at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75) Caused by: java.lang.ClassNotFoundException: org.springframework.web.servlet.config.annotation.WebMvcConfigurerat java.net.URLClassLoader.findClass(Unknown Source)at java.lang.ClassLoader.loadClass(Unknown Source)at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)at java.lang.ClassLoader.loadClass(Unknown Source)... 41 more </error>

I am pretty new to this. I have tried to learn and write the tests and the above code. I am badly stuck at this. Any help is welcome.

Also I do not have any ApplicationContext defined for the Controller as I am using it as external jar in another project. Is it possible to create the tests in the same way.

We only need to test this one controller with two endpoints. Thanks in advance.



Solution

  • This error arose because I had not involved the spring-webmvc dependency in my pom.xml. The reason I was not including it was beacuse I was getting error "AbstractMethodError". But later I found out that the main problem was because I was mixing up spring-boot and spring together.

    Spring-starter-test comes with spring-boot and hence the whole project was collapsing. Thanks to people who responded.