pact-js icon indicating copy to clipboard operation
pact-js copied to clipboard

Cannot use eachLike when root of body is an array and providing multiple examples

Open Mark-J-Lawrence opened this issue 5 years ago • 7 comments

Software versions

Pact JS v9.5.0

Expected behaviour

I have an endpoint which I firstly want to mock and get a predefined array of objects returned during test, and then secondly I want it verified, which needs to be looser as the original predefined list will likely exist, but there will be additional objects on the array which I can't predetermine.

I was hoping I could use eachLike() for this, for example:

This is the mocked body response I want to receive in testing, which I'd later assert:

[
   {
      keyA: 'valueA'
      keyB: 'valueB'
   },
   {
      keyA: 'ValueC'
      keyB: 'ValueD'
   },
   {
      keyA: 'ValueE',
      keyB: 'ValueF'
   },
   ...
]

So I was hoping to declare this interaction:

await global.provider.addInteraction({
      state: 'records exist',
      uponReceiving: 'a GET for records',
      withRequest: {
        method: 'GET',
        path: '/api/inventory/summary'
      },
      willRespondWith: {
        status: 200,
        body: Matchers.eachLike([
          {
             keyA: 'valueA'
             keyB: 'valueB'
          },
          {
             keyA: 'ValueC'
             keyB: 'ValueD'
          },
          {
             keyA: 'ValueE',
             keyB: 'ValueF'
          },
          ...
       ], { min: 3 })
      }
    });

I was hoping that when Pact is mocking it would return the array, and when verifying, it would just match the types in however many objects were returned.

Actual behaviour

The resulting Pact looks to be returning an array within an array and when mocking fetch seems to be experiencing an Internal Server Error.

{
      "description": "a GET for records"
      "providerState": "records exist",
      "request": {
        "method": "GET",
        "path": '/api/inventory/summary'
      },
      "response": {
        "status": 200,
        "headers": {
        },
        "body": [
          [
            {
              "keyA": "ValueA",
              "keyB": "ValueB"
            },
             {
              "keyA": "ValueC",
              "keyB": "ValueD"
            },
             {
              "keyA": "ValueE",
              "keyB": "ValueF"
            }
          ]
        ],
        "matchingRules": {
          "$.body": {
            "min": 1
          },
          "$.body[*].*": {
            "match": "type"
          }
        }
      },
      "metadata": null
    }

There are a few similar bugs raised but on the Java Implementation:

https://github.com/DiUS/pact-jvm/issues/557 https://github.com/DiUS/pact-jvm/issues/555 https://github.com/DiUS/pact-jvm/issues/562

I think my case differs as I want to provide many objects in the array during the mock, not just one example to base the types on.

Mark-J-Lawrence avatar Jan 21 '20 13:01 Mark-J-Lawrence

Just to add, I could change my code to:

await global.provider.addInteraction({
      state: 'records exist',
      uponReceiving: 'a GET for records',
      withRequest: {
        method: 'GET',
        path: '/api/inventory/summary'
      },
      willRespondWith: {
        status: 200,
        body: Matchers.eachLike({
             keyA: 'valueA'
             keyB: 'valueB'
          }, { min: 3 })
      }
    });

which would result is this pact (which likely is how its designed to work):

{
      "description": "a GET for records"
      "providerState": "records exist",
      "request": {
        "method": "GET",
        "path": '/api/inventory/summary'
      },
      "response": {
        "status": 200,
        "headers": {
        },
        "body": [
          {
              "keyA": "ValueA",
              "keyB": "ValueB"
            }
        ],
        "matchingRules": {
          "$.body": {
            "min": 1
          },
          "$.body[*].*": {
            "match": "type"
          }
        }
      },
      "metadata": null
    }

But that doesn't help my scenario, as during the mocking process, only 1 record exists in the array and I require more etc

Mark-J-Lawrence avatar Jan 21 '20 13:01 Mark-J-Lawrence

hmm something seems amiss there. If you specify {min: 3} as per your second example, it should return 3 objects in the list during mocking. Let me confirm this, as I think that is what you're after and is also how it is intended to be used

mefellows avatar Jan 21 '20 21:01 mefellows

Actually, our e2e test example has a list response where it expects (at least) 2 animals to be returned in a list:

See https://github.com/pact-foundation/pact-js/blob/master/examples/e2e/test/consumer.spec.js#L156-L167

The tests will fail if there isn't two responses, so this shows the mock server definitely returns multiple items.

Can you please double check your code and pact generation? I can see that your code mentions {min: 3} but the pact file still has it set to 1.

mefellows avatar Jan 21 '20 21:01 mefellows

The { min: 3 } was a mess up on my part, I'm having to sanitise what data I'm showing in this bug report due to the nature of my work etc. I messed up somewhere when writing up the bug. I can confirm if I set min in the interaction it is successfully set in the Pact. I'll give an example below.

I've looked at the test, I think what I'm wanting to do is a bit different. You're still only defining one record in animalBodyExpectation, and you're using min to return X amount of objects in the array that based on the types given in the sample object in animalBodyExpectation. I'm guessing that the data in these records mirror that given in animalBodyExpectation? The difference in what I want to do is that I want to define all the data of the in the min number of records. So if min = 9, I will provide 9 objects to the eachLike() matcher to be returned when it is mocking. When it is verifying, I hoped it would expect at least 9 objects, but it wouldn't care about the data, only the types etc.

I'm probably trying to get Pact to do something it doesn't do right now, but this is what I've seen setting min does for my scenario:

await global.provider.addInteraction({
      state: 'records exist',
      uponReceiving: 'a GET for all records',
      withRequest: {
        method: 'GET',
        path: '/api/v1/inventory/records',
        headers: {
          Authorization: Matchers.string('')
        }
      },
      willRespondWith: {
        status: 200,
        body: Matchers.eachLike([
           {
              "keyA": "ValueA",
              "keyB": "ValueB"
            },
           {
              "keyA": "ValueC",
              "keyB": "ValueD"
            },
           {
              "keyA": "ValueE",
              "keyB": "ValueF"
            },
           {
              "keyA": "ValueG",
              "keyB": "ValueH"
            }
        ], { min: 4 })
      }
    });

results in the following pact:

{
      "description": "a GET for all records",
      "providerState": "records exist",
      "request": {
        "method": "GET",
        "path": "/api/v1/inventory/records",
        "headers": {
          "Authorization": ""
        },
        "matchingRules": {
          "$.headers.Authorization": {
            "match": "type"
          }
        }
      },
      "response": {
        "status": 200,
        "headers": {
        },
        "body": [
          [
            {
              "keyA": "ValueA",
              "keyB": "ValueB"
            },
            {
              "keyA": "ValueC",
              "keyB": "ValueD"
            },
            {
              "keyA": "ValueE",
              "keyB": "ValueF"
            },
            {
              "keyA": "ValueG",
              "keyB": "ValueH"
            }
          ],
          [
            {
              "keyA": "ValueA",
              "keyB": "ValueB"
            },
            {
              "keyA": "ValueC",
              "keyB": "ValueD"
            },
            {
              "keyA": "ValueE",
              "keyB": "ValueF"
            },
            {
              "keyA": "ValueG",
              "keyB": "ValueH"
            }
          ],
          [
            {
              "keyA": "ValueA",
              "keyB": "ValueB"
            },
            {
              "keyA": "ValueC",
              "keyB": "ValueD"
            },
            {
              "keyA": "ValueE",
              "keyB": "ValueF"
            },
            {
              "keyA": "ValueG",
              "keyB": "ValueH"
            }
          ],
          [
            {
              "keyA": "ValueA",
              "keyB": "ValueB"
            },
            {
              "keyA": "ValueC",
              "keyB": "ValueD"
            },
            {
              "keyA": "ValueE",
              "keyB": "ValueF"
            },
            {
              "keyA": "ValueG",
              "keyB": "ValueH"
            }
          ]
        ],
        "matchingRules": {
          "$.body": {
            "min": 4
          },
          "$.body[*].*": {
            "match": "type"
          }
        }
      },
      "metadata": null
    },

It just seems to duplicate my list of objects four times.

Is what I'm doing something that Pact doesn't support?

If so, I can work around it by defining the interactions twice. The first time I'll mark it for non-verification by the provider and it'll just return all the data I require it to for mocking. The second time I'll specify the interaction as you have in the test file you shared, but I won't use it for mocking, it'll be purely for verification.

Mark-J-Lawrence avatar Jan 22 '20 13:01 Mark-J-Lawrence

I've looked at the test, I think what I'm wanting to do is a bit different

Yes, that's correct. What's currently supported is a single entry that can be repeated (so for your test, you won't get the 9 different objects).

It might be worth looking at why you need those different objects for this test, it smells as if perhaps you might be doing a functional test beyond the scope of what's needed for the contract test. Could you elaborate a bit further? It might be feature request worthy.

mefellows avatar Jan 22 '20 21:01 mefellows

@mefellows I've mentioned what I'm trying to achieve in https://github.com/pact-foundation/pact-node/pull/203

It is beyond what is the minimum requirement for a contract test....I'm essentially using Pact as a fully mocked replacement for a service by providing it all the data I'd like to be returned. As an over-the-top example. If I had a grid in a GUI that called to an external API for data, Pact in this case would be mocking the external API, and if I provided it with 10,000 rows of test data in the interaction, that is what it would return, and if it had a few additional rows, the verification shouldn't fail etc.

Mark-J-Lawrence avatar Jan 30 '20 13:01 Mark-J-Lawrence

Gotcha. Yes, at the moment it's not something we do support. I'll mark as an enhancement for discussion around v4 spec stuff.

mefellows avatar Feb 07 '20 11:02 mefellows