2

In this example I face a problem to write a junit test, which consumes a HAL-formatted rest-service. As I understand I can use MockRestServiceServer to fake a communication. You can find here two simple spring applications; one which provides a HAL-formatted rest-service and one which consumes it. Everything works fine, when both services are started up.

So, when you start both services with the command mvn spring-boot:run and you navigate to http://localhost:8080/products/list, then you can see the consumed rest-service.

The consumer itself uses a modified RestTemplate to request a response entity from the type PagedResources. My consumer-test defines a response body, which looks exactly similar to a normal request from the "provider" service.

My Test

package consumer;

import static org.mockito.Mockito.when;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import static org.springframework.test.util.AssertionErrors.assertEquals;

import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestTemplate;

import consumer.model.Product;
import consumer.service.ProductServiceImpl;
import consumer.service.util.CustomRestTemplate;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = ConsumerApplication.class)
@WebAppConfiguration
@IntegrationTest
public class ConsumerApplicationTests {
    private MockRestServiceServer mockServer;
    private RestTemplate restTemplate;

    @Mock
    private CustomRestTemplate customRestTemplate;

    @InjectMocks
    private ProductServiceImpl productServiceImpl;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        this.restTemplate = new RestTemplate();
        this.mockServer = MockRestServiceServer.createServer(this.restTemplate);
        String responseBody = "\"_links\" : {    \"self\" : {      \"href\" : \"http://localhost:1234/products/list\"    }  },  \"_embedded\" : {    \"products\" : [ {      \"name\" : \"Product 1\",      \"price\" : 0.99,      \"_links\" : {        \"self\" : {          \"href\" : \"http://localhost:1234/v1/products/list/product/1\"        }      }    }]  }}";
        this.mockServer.expect(requestTo("http://localhost:1234/products?size=" + Integer.MAX_VALUE)).andExpect(method(HttpMethod.GET))
        .andRespond(withSuccess(responseBody, MediaType.parseMediaTypes("application/hal+json").get(0)));
    }

    @Test
    public void contextLoads() {
        when(customRestTemplate.getRestTemplateJackson2HttpMessageConverter()).thenReturn(restTemplate);

        List<Product> productList = productServiceImpl.retrieveAllProduct();
        assertEquals("Expected one product", 1, productList.size());
        mockServer.verify();
    }

}

When I execute the test I receive that error:

org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Can not instantiate value of type [simple type, class org.springframework.hateoas.PagedResources<consumer.model.Product>] from String value ('_links'); no single-String constructor/factory method
at [Source: java.io.ByteArrayInputStream@1966492; line: 1, column: 1]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class org.springframework.hateoas.PagedResources<consumer.model.Product>] from String value ('_links'); no single-String constructor/factory method
at [Source: java.io.ByteArrayInputStream@1966492; line: 1, column: 1]
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:208)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:200)
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:97)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:809)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:793)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:572)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:530)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:476)
at consumer.service.ProductServiceImpl.retrieveAllProduct(ProductServiceImpl.java:35)
at consumer.ConsumerApplicationTests.contextLoads(ConsumerApplicationTests.java:58)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:73)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:73)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:224)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:83)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:68)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:163)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class org.springframework.hateoas.PagedResources<consumer.model.Product>] from String value ('_links'); no single-String constructor/factory method
at [Source: java.io.ByteArrayInputStream@1966492; line: 1, column: 1]
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:770)
at com.fasterxml.jackson.databind.deser.ValueInstantiator._createFromStringFallbacks(ValueInstantiator.java:277)
at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createFromString(StdValueInstantiator.java:289)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1141)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:135)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:126)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3066)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2221)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:205)
... 39 more

If I am not completely wrong, the error occurrs because PagedResources does not have a String constructor. So what would be the best solution to test that code?

Thank you in advance!

1 Answers1

1

Check this answer. It was very useful for me. Basically, what you have todo is to remove the PagedResourcesAssembler parameter from the Controller. And build it into the method.

@RequestMapping(method=RequestMethod.GET)
public ResponseEntity<PagedResources<AccountResource>> getAccounts(
        @RequestParam(value="name", required = false) String name, 
        @RequestParam(value="username", required = false) String username, 
        @RequestParam(value="email", required = false) String email, 
        @RequestParam(value="lastName", required = false) String lastName,
        @RequestParam(value="size", required = true, defaultValue = "10") Integer size,
        @RequestParam(value="page", required = true, defaultValue = "0") int page,
        @RequestParam(value="sort", required = false, defaultValue = "username") String sort,
        @RequestParam(value="direction", required = false, defaultValue = "asc") String direction,
        UriComponentsBuilder uriBuilder,
        HttpServletRequest request, 
        HttpServletResponse response) {

    // Build page request
    AccountList list = null;
    Page<Account> resultPage = null;
    Direction sortDirection = Direction.ASC;
    if(direction.equals("desc")) {
        sortDirection = Direction.DESC;
    }
    PageRequest pReq = new PageRequest(page, size, sortDirection, sort);

    resultPage = accountService.findAll(pReq);

    HateoasPageableHandlerMethodArgumentResolver resolver = new HateoasPageableHandlerMethodArgumentResolver();
    PagedResourcesAssembler<Account> accountPageAssembler = new PagedResourcesAssembler<Account>(resolver, null);
    return new ResponseEntity<PagedResources<AccountResource>>(accountPageAssembler.toResource(resultPage, new AccountResourceAsm()), HttpStatus.OK);
}