无法让 Cypress 和 Pact 一起工作

Cannot make Cypress and Pact work together

我已经有几个通过赛普拉斯测试的工作项目。 现在我正在尝试使用 Cypress + Pact

添加合同测试

在开发者控制台中,我可以看到该应用正在调用 /api/v1/document-service,但我得到:

Pact verification failed - expected interactions did not match actual.

部分日志:

W, [2021-07-20T12:49:37.157389 #34805]  WARN -- : Verifying - actual interactions do not match expected interactions.

Missing requests:
    POST /api/v1/document-service

W, [2021-07-20T12:49:37.157489 #34805]  WARN -- : Missing requests:
    POST /api/v1/document-service

我正在使用:

  cypress: 7.5.0
  @pact-foundation/pact: 9.16.0

我完成的步骤:

  1. 添加了 cypress 插件(https://github.com/pactflow/example-consumer-cypress/blob/master/cypress/plugins/cypress-pact.js

  2. 添加命令(https://github.com/pactflow/example-consumer-cypress/blob/master/cypress/support/commands.js

  3. 已将配置添加到 cypress.json (https://github.com/pactflow/example-consumer-cypress/blob/master/cypress.json) - 如果我不想与真实服务器交互,则不确定要将什么放入 baseUrl。

  4. 添加测试:

    let server;
    
    describe('Simple', () => {
        before(() => {
            cy.mockServer({
                consumer: 'example-cypress-consumer',
                provider: 'pactflow-example-provider',
            }).then(opts => {
                cy.log(opts)
                server = opts
            })
        });
    
        beforeEach(() => {
            cy.fakeLogin()
    
            cy.addMockRoute({
                server,
                as: 'products',
                state: 'products exist',
                uponReceiving: 'a request to all products',
                withRequest: {
                    method: 'POST',
                    path: '/api/v1/document-service',
                },
                willRespondWith: {
                    status: 200,
                    body: {
                        data: {
                            collections: [
                                {
                                    id: '954',
                                    name: 'paystubs',
                                },
                                {
                                    id: '1607',
                                    name: 'mystubs',
                                },
                            ],
                        },
                    },
                },
            });
        });
        it('is ok?', () => {
            cy.visit('/new/experiments/FirstProject/collections');
        });
    })
    

尝试使用已弃用的 cy.server()/cy.route() 和新的 cy.intercept(),但仍然验证失败。

在 Pactflow,我们花了大约 6 个月的时间一起使用 Cypress 和 Pact,按照 https://pactflow.io/blog/cypress-pact-front-end-testing-with-confidence/

中的建议,使用 Pact 模拟服务从我们的 Cypress 测试中生成契约

我们本周决定改变我们的方法,原因如下。

  1. 我们的 Cypress 测试比我们的单元测试慢得多(15 分钟以上),因此当我们从 Cypress 测试生成契约与我们的单元测试相比,生成契约,然后验证它需要更长的时间。
  2. 我们的 Cypress 测试可能会因为与 Pact 无关的原因而失败(例如布局更改、构建节点上的内存消耗问题、UI 库升级等),这会阻止来自 Pact 的协议正在生成。
  3. Cypress 的 intercept() 和 mock 服务不能很好地协同工作。任何没有针对 Cypress 的显式 intercept() 的请求最终都会在模拟服务处结束,并且每次页面使用新端点时,请求都会命中模拟服务,然后整体测试失败,即使该端点该特定测试不需要。
  4. 当 Cypress 测试失败时,Cypress 库提供了非常好的调试功能。当它由于与 Pact 有关的原因而失败时,目前很难确定原因,因为该信息不会显示在浏览器中(这可能会得到改进,但是,它不会缓解已经列出的问题)。

我们的新方法是从我们的单元测试(而不是我们的 Cypress 测试)生成我们的 Pacts,并使用剥离 Pact 匹配器的函数在单元测试和 Cypress 测试之间共享固定装置。例如

const SOME_INTERACTION = {
  state: 'some state',
  uponReceiving: "some request",
  withRequest: {
    method: "GET",
    path: "/something"
  },
  willRespondWith: {
    status: 200,
    body: {
      something: like("hello")
    }
  }
}

单元测试

describe('Something', () => {
  let someProviderMockService
  beforeAll(() => {
    someProviderMockService = new Pact({
      consumer: 'some-consumer',
      provider: 'some-provider'
    })
    return someProviderMockService.setup()
  })
  afterAll(() => someProviderMockService.finalize())
  afterEach(() => someProviderMockService.verify())

  describe('loadSomething', () => {
    beforeEach(() => {
      return someProviderMockService.addInteraction(SOME_INTERACTION)
    })

    it('returns something', async () => {
      const something = await loadSomething()
      //expectations here
    })
  })
})

将 Pact 交互格式转换为 Cypress 路由格式并去除 Pact 匹配器的函数。

const Matchers = require('@pact-foundation/pact-web').Matchers

// TODO map the request body and query parameters too
const toCypressInteraction = (interaction) => {
  return {
    method: interaction.withRequest.method,
    url: Matchers.extractPayload(interaction.withRequest.path),
    status: interaction.willRespondWith.status,
    headers: Matchers.extractPayload(interaction.willRespondWith.headers),
    response: Matchers.extractPayload(interaction.willRespondWith.body)
  }
}

在赛普拉斯测试中

cy.route(toCypressInteraction(SOME_INTERACTION))

这种方法有以下好处:

  1. 契约生成速度很快。
  2. 契约生成可靠。
  3. Cypress 测试更可靠且更易于调试。
  4. 赛普拉斯测试中使用的请求被验证是正确的。
  5. “交互膨胀”的可能性较小,其中添加交互只是为了测试 UI 功能,而不是因为它们提供了有价值的覆盖范围。

希望这些信息对您有所帮助。我们现在推荐这种方法,而不是在 Cypress 测试中直接使用模拟服务。