Pacts: Matching rule for non-empty map (or a field which is not null) needed

1.7k views Asked by At

I need help with writing my consumer Pacts using pact-jvm (https://github.com/DiUS/pact-jvm).

My problem is I have a field which is a list (an array) of maps. Each map can have elements of different types (strings or sub-maps), eg.

"validatedAnswers": [
    {
      "type": "typeA",
      "answers": {
        "favourite_colour": "Blue",
        "correspondence_address": {
            "line_1": "Main St",
            "postcode": "1A 2BC",
            "town": "London"
        }
      }
    },
    {
      "type": "typeB",
      "answers": {
        "first_name": "Firstname",
        "last_name": "Lastname",
      }
    }
  ]

but we're only interested in some of those answers.

NOTE: The above is only an example showing the structure of validatedAnswers. Each answers map has dozens of elements.

What we really need is this: https://github.com/pact-foundation/pact-specification/issues/38, but it's planned for v.4. In the meantime we're trying a different approach. What I'm attempting to do now is to specify that each element of the list is a non-empty map. Another approach is to specify that each element of the list is not null. Can any of this be done using Groovy DSL?

This:

new PactBuilder().serviceConsumer('A').hasPactWith('B')
.port(findAvailablePort()).uponReceiving(...)
.willRespondWith(status: 200, headers: ['Content-Type': 'application/json'])
.withBody {
  validatedAnswers minLike(1) {
     type string()
     answers {
     }
  }
}

doesn't work because it mean answers is expected to be empty ("Expected an empty Map but received Map( [...] )", see also https://github.com/DiUS/pact-jvm/issues/298).

So what I would like to do is something like this:

.withBody {
    validatedAnswers minLike(1) {
         type string()
         answers Matchers.map()
    }
}

or:

validatedAnswers minLike(1) {
     type string()
     answers {
             keyLike 'title', notNull()
     }
}

or:

validatedAnswers minLike(1) {
     type string()
     answers notNull()
}

Can it be done?

2

There are 2 answers

1
Dmitry Shaldin On

You can do it without DSL, sample Groovy script:

class ValidateAnswers {
    static main(args) {
        /* Array with some samples */
        List<Map> answersList = [
            [
                type: 'typeA',
                answers: [
                    favourite_colour: 'Blue',
                    correspondence_address: [
                        line_1: 'Main St',
                        postcode: '1A 2BC',
                        town: 'London'
                    ]
                ]
            ],
            [
                type: 'typeB',
                answers: [
                    first_name: 'Firstname',
                    last_name: "Lastname"
                ]
            ],
            [
                type: 'typeC',
                answers: null
            ],
            [
                type: 'typeD'
            ],
            [
                type: 'typeE',
                answers: [:]
            ]
        ]

        /* Iterating through all elements in list above */
        for (answer in answersList) {
            /* Print result of checking */
            println "$answer.type is ${validAnswer(answer) ? 'valid' : 'not valid'}"
        }
    }

    /**
    * Method to recursive iterate through Map's.
    * return true only if value is not an empty Map and it key is 'answer'.
    */
    static Boolean validAnswer(Map map, Boolean result = false) {
        map.each { key, value ->
            if (key == 'answers') {
                result = value instanceof Map && value.size() > 0
            } else if (value instanceof Map) {
                validAnswer(value as Map, false)
            }
        }

        return result
    }
}

Output is:

typeA is valid
typeB is valid
typeC is not valid
typeD is not valid
typeE is not valid
1
Matthew Fellows On

I would create two separate tests for this, one test for each of the different response shapes and have a provider state for each e.g. given there are type b answers.

This way when you verify on provider side, it will only send those two field types.

The union of the two examples gives a contract that allows both.