Search code examples
spring-bootmockitotddresttemplatemockmvc

Unable to Mock RestTemplate.exchange


As part of TDD i want to be able to test every portion of my SpringBoot rest application. However i am unable to mock external calls.

Application structure
1. Few rest endpoints which internally call external rest endpoints.
2. All calls to external endpoints are orchestrated through a local http client which utilizes RestTemplate as httpClient.

@RunWith(SpringRunner.class)  

@SpringBootTest(webEnvironment = WebEnvironment.MOCK, classes = TestDrivenDevelopmentWithJavaApplication.class)  

public class TestDrivenDevelopmentWithJavaApplicationTests {
@Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
@MockBean
private RestTemplate client;

@Before
public void setup() throws Exception {
    mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    Structure1Root category = new Structure1Root();
    Category cat = new Category();
    cat.setCategoryName("Test1");
    cat.setDescription("Test");
    category.setD(cat);

    Mockito.when(client.exchange(
            ArgumentMatchers.eq("https://services.odata.org/V2/Northwind/Northwind.svc/Products(1)?$format=json"),
            ArgumentMatchers.eq(HttpMethod.GET), ArgumentMatchers.eq(null),
            ArgumentMatchers.eq(Structure1Root.class)))
            .thenReturn(new ResponseEntity<Structure1Root>(category, HttpStatus.OK));
}

@Test
public void testendpoint1() throws Exception {
    mockMvc.perform(MockMvcRequestBuilders.get("/api/endpoint1?token=1").contentType(MediaType.APPLICATION_JSON))
            .andExpect(content().string(org.hamcrest.Matchers.containsString("Test1")));

}

}

Even though i have setup the mock on client.exchange(RestTemplate.exchange), i see response returned by client.exchange is null and not the response specified in thenReturn

Controller Code

@RestController
@RequestMapping(path = Endpoint.base)
public class Endpoint {
    public static final String base = "/api";
    @Autowired
    MyHttpClient<Structure2Root> client;
    @Autowired
    MyHttpClient<Structure1Root> Cclient;

    @GetMapping(path = "/endpoint1")
    public ResponseEntity<Structure2Root> callEndpt1(@RequestParam String token) {
        Response<Structure2Root> resp = client
                .execute("https://services.odata.org/V2/Northwind/Northwind.svc/Products(1)?$format=json", Structure2Root.class);
        return new ResponseEntity<Structure2Root>(resp.getResponse(), HttpStatus.OK);
    }

    @GetMapping(path = "/endpoint2")
    public ResponseEntity<Structure1Root> callEndpt2(@RequestParam String token) {
        Response<Structure1Root> resp = Cclient.execute(
                "https://services.odata.org/V2/Northwind/Northwind.svc/Categories(1)?$format=json", Structure1Root.class);
        return new ResponseEntity<Structure1Root>(resp.getResponse(),HttpStatus.OK);
    }

}

And finally, local http client code

@Service
public class MyHttpClient<O> {
    @Autowired
    RestTemplate client;

    public MyHttpClient() {
        // TODO Auto-generated constructor stub
    }

    public Response<O> execute(String url, Class<O> generic) {
        ResponseEntity<O> resp = client.exchange(url, HttpMethod.GET, null, generic);
        return new Response<O>(resp.getStatusCode(), resp.getBody());
    }

}

this client.execute is what i intend to intercept in the first code block

However never seems to work and always returns a null. The Git Repo

Regards,
Veera


Solution

  • You have used the wrong object while mocking. You should be using Structure2Root rather then Structure1Root

    The correct test class is below which is working perfectly fine.

    package com.demo.samples.tdd;
    
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
    
    import com.demo.samples.tdd.responses.Product;
    import com.demo.samples.tdd.responses.Structure2Root;
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.mockito.ArgumentMatchers;
    import org.mockito.Mockito;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
    import org.springframework.boot.test.mock.mockito.MockBean;
    import org.springframework.http.HttpMethod;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.http.ResponseEntity;
    import org.springframework.test.context.junit4.SpringRunner;
    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.client.RestTemplate;
    import org.springframework.web.context.WebApplicationContext;
    
    import com.demo.samples.tdd.responses.Category;
    import com.demo.samples.tdd.responses.Structure1Root;
    
    @RunWith(SpringRunner.class)
    
    @SpringBootTest(webEnvironment = WebEnvironment.MOCK, classes = TestDrivenDevelopmentWithJavaApplication.class)
    public class TestDrivenDevelopmentWithJavaApplicationTests {
        @Autowired
        private WebApplicationContext webApplicationContext;
        private MockMvc mockMvc;
        @MockBean
        private RestTemplate client;
    
        @Before
        public void setup() throws Exception {
            mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    //      Structure1Root category = new Structure1Root();
    //      Category cat = new Category();
    //      cat.setCategoryName("Test1");
    //      cat.setDescription("Test");
    //      category.setD(cat);
    //
    //      Mockito.when(client.exchange(
    //              ArgumentMatchers.eq("https://services.odata.org/V2/Northwind/Northwind.svc/Products(1)?$format=json"),
    //              ArgumentMatchers.eq(HttpMethod.GET), ArgumentMatchers.eq(null),
    //              ArgumentMatchers.eq(Structure1Root.class)))
    //              .thenReturn(new ResponseEntity<Structure1Root>(category, HttpStatus.OK));
    
    
            Structure2Root category2 = new Structure2Root();
            Product product = new Product();
            product.setProductName("Test1");
            product.setUnitPrice("1");
            category2.setD(product);
    
            Mockito.when(client.exchange(
                    ArgumentMatchers.eq("https://services.odata.org/V2/Northwind/Northwind.svc/Products(1)?$format=json"),
                    ArgumentMatchers.eq(HttpMethod.GET), ArgumentMatchers.eq(null),
                    ArgumentMatchers.eq(Structure2Root.class)))
                    .thenReturn(new ResponseEntity<Structure2Root>(category2, HttpStatus.OK));
    
    
        }
    
        @Test
        public void testendpoint1() throws Exception {
            mockMvc.perform(MockMvcRequestBuilders.get("/api/endpoint1?token=1").contentType(MediaType.APPLICATION_JSON))
                    .andExpect(content().string(org.hamcrest.Matchers.containsString("Test1")));
    
        }
    
    }