Pact: no HttpMessageConverter found for response type

714 views Asked by At

So I have two Spring boot projects. In project A I call out to project B's controller via a service class to retrieve a ResponseEntity containing a list of strings. Project A's service class being the consumer and B being the provider in Pact terminology:

BookServiceInterfaceImpl.java

@Service("bookService")
public class BookServiceInterfaceImpl implements BookServiceInterface {

public ResponseEntity<List<String>> getBookTitlesForCourse(final String courseId){
    RestTemplate restTemplate = new RestTemplate();
    ResponseEntity<String[]> response = restTemplate.getForEntity("http://localhost:8888/api/book/course/" + courseId, String[].class);
    List<String> titles = Arrays.asList(response.getBody());
    return new ResponseEntity<List<String>>(titles, HttpStatus.OK);
}
}

BookController.java

@RestController
@RequestMapping("/api")
public class BookController {

private BookService bookService;

@Autowired
public BookController(BookService bookService) {
    this.bookService = bookService;
}

@RequestMapping(method = RequestMethod.GET, value="/book/course/{id}")
public ResponseEntity<String[]>getBookTitlesForCourse(@PathVariable("id") final long id) {
    List<Book> books = bookService.findAllBooks();
    if(books.isEmpty()) {
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }
    String[] bookTitles = books.stream()
            .filter(book -> book.getCourseId() == id)
            .map(book -> book.getTitle())
            .toArray(String[]::new);
    return new ResponseEntity<String[]>(bookTitles, HttpStatus.OK);
}
}

I'm trying to use Pact to create a contract between the two. Thus far I have the following test in the consumer:

BookServiceTest.java

@RunWith(SpringRunner.class)
@SpringBootTest
public class BookServiceTest {

@Rule
public PactProviderRuleMk2 mockProvider = new PactProviderRuleMk2("test_provider", "localhost", 8080, this);

@Pact(provider="test_provider", consumer="test_consumer")
public RequestResponsePact createPact(PactDslWithProvider builder) {
    return builder
            .given("test state")
            .uponReceiving("BookServiceTest test interaction")
                .path("/api/book/course/1")
                .method("GET")
            .willRespondWith()
                .status(200)
                .body("{[\"Dont make me think\",\"Clean Code\"]}")
            .toPact();
}

@Test
@PactVerification("test_provider")
public void test_retrieveBooksForcourse_validCourseId_success() {
    //given
    final String courseId = "1";
    BookServiceInterfaceImpl bookService = new BookServiceInterfaceImpl();
    //when
    ResponseEntity<List<String>> response = bookService.getBookTitlesForCourse(courseId);
    //then:
    assertThat(response.getStatusCode(), is(200));
    assertThat(response.getBody().size(), is(2));
}
}

And the pom file for the consumer is:

pom.xml

<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>

<groupId>com.ocr</groupId>
<artifactId>new-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>new-consumer</name>
<description>Demo project for Spring Boot</description>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.6.RELEASE</version>
    <relativePath />
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>au.com.dius</groupId>
        <artifactId>pact-jvm-consumer-junit_2.11</artifactId>
        <version>3.5.5</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

When I run this, I get the following:

java.lang.AssertionError: Pact Test function failed with an exception: Could not extract response: no suitable HttpMessageConverter found for response type [class [Ljava.lang.String;] and content type [application/octet-stream]
at au.com.dius.pact.consumer.BaseProviderRule.validateResult(BaseProviderRule.java:164)
at au.com.dius.pact.consumer.BaseProviderRule$1.evaluate(BaseProviderRule.java:77)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
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:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
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: org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class [Ljava.lang.String;] and content type [application/octet-stream]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:110)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:917)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:901)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:655)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613)
at org.springframework.web.client.RestTemplate.getForEntity(RestTemplate.java:312)
at com.ocr.newconsumer.service.BookServiceInterfaceImpl.getBookTitlesForCourse(BookServiceInterfaceImpl.java:29)
at com.ocr.newconsumer.controller.BookServiceTest.test_retrieveBooksForcourse_validCourseId_success(BookServiceTest.java:60)
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.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at au.com.dius.pact.consumer.BaseProviderRule.lambda$runPactTest$1(BaseProviderRule.java:150)
at au.com.dius.pact.consumer.BaseMockServer.runAndWritePact(MockHttpServer.kt:152)
at au.com.dius.pact.consumer.ConsumerPactRunnerKt.runConsumerTest(ConsumerPactRunner.kt:13)
at au.com.dius.pact.consumer.BaseProviderRule.runPactTest(BaseProviderRule.java:148)
at au.com.dius.pact.consumer.BaseProviderRule.access$100(BaseProviderRule.java:21)
at au.com.dius.pact.consumer.BaseProviderRule$1.evaluate(BaseProviderRule.java:76)
... 20 more
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at au.com.dius.pact.consumer.BaseProviderRule.lambda$runPactTest$1(BaseProviderRule.java:150)
at au.com.dius.pact.consumer.BaseMockServer.runAndWritePact(MockHttpServer.kt:152)
at au.com.dius.pact.consumer.ConsumerPactRunnerKt.runConsumerTest(ConsumerPactRunner.kt:13)
at au.com.dius.pact.consumer.BaseProviderRule.runPactTest(BaseProviderRule.java:148)
at au.com.dius.pact.consumer.BaseProviderRule.access$100(BaseProviderRule.java:21)
at au.com.dius.pact.consumer.BaseProviderRule$1.evaluate(BaseProviderRule.java:76)
... 20 more

So I'm not what exactly the issue is, from what I know I'm pretty sure this kind of conversion shouldn't be a big thing and should be handled by Spring. Is there something I'm doing incorrectly with regards to the REST call or the test(first time using Pact)? Am I missing a dependency in the pom that I've assumed the Spring Boot parent/dependencies would have?

1

There are 1 answers

3
Jose Martinez On

For our consumer tests we extend ConsumerPactTestMk2. One of the methods that you have to override is runTest(MockServer ms). The MockServer in the runTest method has the host portion of the URL that you need to use. So you need to change the URL in the imple class to use the augmented URL.

public class BookServiceInterfaceImpl implements BookServiceInterface {
    private String hostUrl = "http://localhost:8888";
    //provider setter for this variable.

With that in place then you can use the hostUrl to create your final URL.

ResponseEntity<String[]> response = restTemplate.getForEntity(hostUrl + "/api/book/course/" + courseId, String[].class);

Then in the runTest method.

@Override
protected void runTest(MockServer ms) throws IOException {
    BookServiceInterfaceImpl bookService = new BookServiceInterfaceImpl();
    bookService.setHostUrl(ms.getUrl());

EDIT: Here is an exmaple.