Alan Boshier
03/07/2022, 6:29 PMMatt (pactflow.io / pact-js / pact-go)
Matt (pactflow.io / pact-js / pact-go)
Matt (pactflow.io / pact-js / pact-go)
Shaun Reid
03/08/2022, 8:03 AMwithQuery
and formatted variables format
GQL definition:
import { gql } from '@apollo/client'
export const GQL_QUALIFIED_NAME = gql`
mutation mutationName($type: TokenType!, $token: String!) {
mutationName(type: $type, token: $token) {
transactionId
id
orderNumber
}
}
`
Consumer test file
// These must be static for Pact versioning
const exampleRequestToken= '8b06e062-32e3-41b3-b3ae-fbddb637eca0' // generator: generateFakeGuid(),
const graphQLResult = {
transactionId: '2c8ac019dfbe430d087b1d23', // generator: generateFakeMongoDBId(),
id: 'ac1a5c72-73e8-4d36-bd79-6c1fbfcfb27f', // generator: generateFakeGuid(),
orderNumber: '320403409' // generator: generateFakeExternalOrderId()
}
const requestFormat= {
token: Matchers.regex({ generate: exampleRequestToken, matcher: Matchers.UUID_V4_FORMAT }),
type: REQUEST_TOKEN_TYPE
}
const resultFormat = {
transactionId: Matchers.regex({ generate: graphQLResult.exampleResultId, matcher: '^[0-9a-f]{24}$' }),
id: Matchers.regex({ generate: graphQLResult.exampleResultGUID, matcher: Matchers.UUID_V4_FORMAT }),
orderNumber: Matchers.regex({ generate: graphQLResult.exampleOrderNumber, matcher: '^[0-9]{9}$' })
}
// Define Interaction on client
new ApolloGraphQLInteraction()
.uponReceiving('internal test name')
.given('pact scenario name must be identical in provider and consumer')
.withOperation('mutationName')
.withQuery(print(GQL_QUALIFIED_NAME))
.withRequest({
path: '/graphql',
method: 'POST'
})
.withVariables(requestFormat)
.willRespondWith({
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
body: {
data: {
mutationName: Matchers.like(resultFormat)
}
}
})
)
// Execute interaction on client
await expect(
client.mutate({
mutation: GQL_QUALIFIED_NAME ,
variables: {
token: exampleRequestToken,
type: REQUEST_TOKEN_TYPE
}
})
).resolves.toEqual({
data: {
mutationName: graphQLResult
}
})
Matt (pactflow.io / pact-js / pact-go)
Shaun Reid
03/08/2022, 8:22 AMnote: we didn't resolve getting typedefs dynamically built at test time, so they are manually added here
import express from 'express'
import { Express } from 'express-serve-static-core'
import { ApolloServer, gql } from 'apollo-server-express'
import { Server } from 'http'
// TODO really needs to use scheama.gql, and not be manually updated
const typeDefs = gql`
enum TokenType {
TOKEN_TYPE_A
TOKEN_TYPE_B
}
`
export default class GraphQLTestServerFactory {
apolloServer: ApolloServer
expressServer: Express
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
constructor(resolvers) {
// Provide resolver functions for your schema fields
this.apolloServer = new ApolloServer({ typeDefs, resolvers })
this.expressServer = express()
this.apolloServer.applyMiddleware({ app: this.expressServer })
}
listen({ port }: { port: number }): Server {
return this.expressServer.listen({ port })
}
}
Then jest problems, but it provided enough flexibility to test multiple scenarios
Provider Test file:
let testFactory: GraphQLTestServerFactory
const mutationNameMock = jest.fn()
beforeAll(() => {
jest.setTimeout(30000) // IO is slow sometimes
const resolvers = {
Mutation: {
mutationName: (_, params) => mutationNameMock(params),
},
}
testFactory = new GraphQLTestServerFactory(resolvers)
})
// Test
describe('Pact Verification', () => {
it('validates charge Paydock with token', async done => {
const opts: VerifierOptions = {
stateHandlers: {
'pact scenario name must be identical in provider and consumer': async () => {
mocked(serviceA.internalFunctionA).mockResolvedValueOnce(valueA)
mocked(serviceA.internalFunctionA).mockResolvedValueOnce(valueB)
mocked(mutationNameMock).mockImplementationOnce(({ token, type }) =>
mutationsClass.mutationNameMethod(type, token),
)
return Promise.resolve('this worked')
},
'a different pact scenario': async () => {
mocked(serviceA.internalFunctionA).mockResolvedValueOnce(valueA)
mocked(serviceA.internalFunctionA).mockResolvedValueOnce(valueC)
mocked(mutationNameMock).mockImplementationOnce(({ token, type }) =>
mutationsClass.mutationNameMethod(type, token),
)
return Promise.resolve('this worked')
},
'a failing pact scenario': async () => {
mocked(serviceA.internalFunctionA).mockResolvedValueOnce(valueA)
mocked(serviceA.internalFunctionA).mockResolvedValueOnce(undefined)
mocked(mutationNameMock).mockImplementationOnce(({ token, type }) =>
mutationsClass.mutationNameMethod(type, token),
)
return Promise.resolve('this worked')
},
},
provider: 'provider-service-name',
consumerVersionSelectors: [{ tag: 'master', latest: true }],
pactBrokerUrl: process.env.PACT_BROKER_BASE_URL,
enablePending: true,
includeWipPactsSince: '2020-01-01',
providerVersion: process.env.BUILDKITE_COMMIT,
providerVersionTags: process.env.BUILDKITE_BRANCH,
providerBaseUrl: '<http://localhost:4002>',
publishVerificationResult: <http://process.env.CI|process.env.CI> === 'true',
logLevel: 'info',
}
const output = await new Verifier(opts).verifyProvider()
console.log(output)
done()
})
})
Shaun Reid
03/08/2022, 8:23 AMAlan Boshier
03/08/2022, 10:55 AMShaun Reid
03/08/2022, 11:10 PMMatt (pactflow.io / pact-js / pact-go)
Alan Boshier
03/09/2022, 8:25 AMAlan Boshier
03/09/2022, 10:43 AM