Skip to content

Instantly share code, notes, and snippets.

@bethesque
Last active October 31, 2022 18:10
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bethesque/43eef1bf47afea4445c8b8bdebf28df0 to your computer and use it in GitHub Desktop.
Save bethesque/43eef1bf47afea4445c8b8bdebf28df0 to your computer and use it in GitHub Desktop.
Using pact with providers that have their own providers

Eg. A -> B -> C

A more find grained view of this intergration might go:

|------------------A Codebase------------------||------------------B Codebase------------------||--------C Codebase---...
 A UI -> A Controller -> B Service -> B Client -> B API -> B Controller -> CService -> CClient -> C API etc...  
                                           |---A/B pact------------------------?
                                           |---A/B pact------------------------------------?
                                           |---A/B pact----------------------------------------------?

The recommended approach for creating the A/B Pact is to write Pact tests for the BClient class (testing the "back door" of A and the "front door" of B). When you verify the A/B Pact, it's best to stub out the calls to C at some level. You may choose to stub at the Service, Client or API (HTTP) level. Which ever level you choose, you then need to make sure that you have another test that ensures that the real compontent behaves the same way as the stubbed component, just like Pact all over again. You'll need some sort of "contract" to keep those tests in sync, whether that contract is another Pact, or a "shared artifact" (more on that below).

1. Stub the C client or service class

Assuming that all your interactions between B and C go through a "CClient" class, you can create a stub implementation of CClient that allows you to set arbitrary responses during your provider state set up call.

class CClientStubImpl implements CClient {
  
  private Alligator alligator;


  public void stubAlligator(Alligator alligator) {
    this.alligator = alligator;
  }

  @Override
  public Alligator getAlligator(String name) {
    return alligator;
  }

}

class CStubbedObjects {
  public static final Alligator MARY = new Alligator("Mary");

}

During provider state set up:

cclient.stubAlligator(CStubbedObjects.MARY);


Then, in your Pact test for CClient, you can use the same alligator instance as a "link" to tie your stubbing to an artifact that will get verified.

Ron can you give me an example for pact jvm or groovy?

2. Use the pact that you have generated between B and C to create a stub server

A pact is best created using unit tests for the client class. Ideally, you could have a CClient in your B codebase, and you would write your Pact tests using that CClient class.

Once you have generated that pact in the B unit tests, it can than be used with one of the Pact stub server implementations to run up an HTTP mock during the A/B verification.

One thing to beware of with this approach is that you'll want to use the type/regular expression based matching (the "flexible" matching) when you set up your expected requests in your B/C Pact tests. Usually, we would advise that your request expectations use exact values, as you're in full control of the HTTP request that is sent to the mock server in a unit test. However, if your outgoing request to C is being generated from your incoming request from A during a Pact verification, you can't control what is going to be in that request, so you'll want to be as flexible as possible.

@rholshausen
Copy link

Do you need me to provide an example of using a mock client, or an example of a "link" to tie your stubbing to an artifact that will get verified?

I haven't had much success with using option 2. The problem is the data used in the Pact between B and C could be very different as from that from A to B. And you want to avoid coupling your tests together with common data.

@TimothyJones
Copy link

Wouldn't it be better to use the Pact state change to stub the B controller? I thought the general pact strategy would be the same regardless of whether B needs a provider or not - this has the advantage that it doesn't matter how many providers there are in the chain.

A->B Pact tests cover B Service -> B Client -> B API
B's unit tests: cover B Controller
B->C Pact tests cover C Service -> C Client -> C API
C's unit tests cover C Controller
(...etc)

This might be a misuse of the word "controller", depending on the model - but I mean the domain layer that the API calls. For a passthrough API, it will be super thin.

@Kabir-Khan
Copy link

Kabir-Khan commented Apr 18, 2022

Is there any full code examples of option 1 and/or 2? It will be much appreciated.

Similar to Option 1 approach, I am thinking of is to us DI at service level when running provider verification in B of consumer contract from A. I will replace the DI container with stubbed container which contains the implementation of the stubbed services. Also I will add new unit test s to ensure that stub services behaves the same way as the real component. Will that be sufficient to say A/B are pact tested ? Or is my approach break any fundamentals of Pact Testing? I am not as to how to use provider state to stub dependencies out!

Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment