ITV/scala-pact

Internal verification of contract on provider

Closed this issue · 4 comments

I've been reading through the docs and trying to understand the best way to test the contract on the provider code. Considering that my provider has some contracts hidden behind chained calls on the API, I would like to be able to write tests as I do on the consumer, ie, one test per interaction. I've seen that the "pact jvm" has the possibility of using the PactVerifyProvider annotation on a test by providing the message for the interaction and checking the returned message.

@PactVerifyProvider('an order confirmation message')
source: https://github.com/DiUS/pact-jvm/blob/master/pact-jvm-provider-gradle/README.md

Looking at the Scala-Pact docs I can see a mention to the internal verification here:
http://io.itv.com/scala-pact/examples/provider.html. Although, all that I can find about internal verification are these examples:

describe("Verifying Consumer Contracts") {

    it("should be able to verify it's contracts") {

      verifyPact
        .withPactSource(loadFromLocal("delivered_pacts"))
        .setupProviderState("given") {
          case "Results: Bob, Fred, Harry" =>
            val newHeader = "Pact" -> "modifiedRequest"
            ProviderStateResult(true, req => req.copy(headers = Option(req.headers.fold(Map(newHeader))(_ + newHeader))))
        }
        .runVerificationAgainst("localhost", 8080)
    }
}

Is there a way to have a test per interaction and having scala Pact generate the input to which the provider should be able to handle and check return the return to check the provider contract?

Now you mention it, we are a bit thin on docs in that area. There's a brief description of the different strategies here but it's more the principle than the practicalities:

http://io.itv.com/scala-pact/articles/verification-strategies.html

Also try taking a look at this, it's the github version of the link you sent but it shows how the service gets set up ahead of the tests being run:

https://github.com/ITV/scala-pact/blob/master/example/provider_tests/src/test/scala/com/example/provider/VerifyContractsSpec.scala

To answer your question directly though, no, there is no way to run a quickcheck/scalacheck style test against each particular endpoint / contracts. I think if you wanted to do that you'd need to do some sort of route test. Pact testing is not a general purpose route testing or acceptance testing tool.

The aim of Pact verification here is to be given a suite of use cases that your customer cares and has defined for you (describing their needs, not yours) and to verify that your service meets their expectations.

Does that help?

@davesmith00000 thank you very much for the information provided, It does indeed help.

I agree with the view on the point of view of Pact not being route testing or acceptance testing tool. To be clear that is not what I am trying to achieve.

I will give you a bit more of context to see if there is anything thing that you can think of that I am clearly missing after reading the documentation.

I have two routes:

def a(req: RequestA) : ReplyA

def b(req: RequestB): ReplyB

case class RequestA(a: Int, b: Boolean)

case class ReplyA(replyAId: Int)

case class RequestB(AId: Int, stuff: String) // AId is value returned by a previous call to RequestB

case class ReplyB(replyBId: Int, status: Boolean, error: Option[String], valueReturned: Option[A])

I can test a by having an interaction that defines a RequestA and expects something that passes as a valid ReplyA.

But in order to test b, when using the real provider, I need to have a valid AId to be able to have the provider return a ReplyB so that I can check that contract.

So that I am clear I don't want to do a real call to a to setup the scenario for b, I just want to have a way to inject a mocked context, so that the class that creates ReplyB on my provider returns an instance of ReplyB for that interaction, let's say Success Vs Error.

Yes that's perfectly possible.

Consider this high level layout of an imaginary microservice.

                +-An-HTTP-Service-----------------------------------------+
                |                                                         |
                | +-------------------+ +--------------+ +--------------+ |
                | |(A)                | |(B)           | |(C)           | |
  Request +---->+ |Routes             | |Business Logic| |Connectors to | |
                | |Marshalling        | |              | |other services| |
                | |Validation         | |              | |etc.          | |
                | |Content negotiation| |              | |              | |
 Response <-----+ |etc.               | |              | |              | |
                | +-------------------+ +--------------+ +--------------+ |
                |                                                         |
                +---------------------------------------------------------+

In terms of pact validation, the only part that actually matters, is part (A).

Given a service in a KNOWN state, the routing / validation / whatever logic will accept my request and the marshalling / negotiation / whatever logic will return the expected response.

The key word is known. Since the state is known, the business logic does not matter. You'll no doubt have tested the business logic with other kinds of tests anyway. Therefore, we can replace the entire business logic of the service with completely fake data and ignore the messy business of calling other services / stubs or databases for example.

Which is what I'm doing here on this line:

https://github.com/ITV/scala-pact/blob/master/example/provider_tests/src/test/scala/com/example/provider/VerifyContractsSpec.scala#L23

Since your business logic is entirely fake, you can use the input values from the pact contracts to return different answers. This is a manual process I'm afraid! :)

This does require work, you will have to construct your service in such as way that the business logic can be injected into the service on start up somehow. But then you can just inject the fake version during a beforeAll statement in your test suite as you start your service.

Hope that helps.

Makes full sense. It does help indeed.

Will go ahead with, when needed, with some matching on request to expected return expected reply. Essentially this will create a "test contract". In order to guarantee that I actually test the part A that you mentioned above, I will use real A, with fake B.

Again thank you very much for the help clarifying matters here, for the work put on maintaining this framework.