模拟系统依赖

Mocking a system dependencies

我正在开发一个调用外部 API 的系统,有些是我公司所有的,有些不是。

我的系统由一个 HTTP 接口组成,该接口接受命令并将它们发布到消息队列中,以便 运行 操作链。我的系统由 3 个 NodeJS 进程(1 个用于 HTTP,2 个消息队列消费者),2 个数据库和一个消息队列组成。

当我开发我的应用程序时,很难测试我的系统涵盖的所有场景(即使我有单元测试)。为了确保所有组件协同工作,我正在使用 Gherkin 语言和 Cucumber js 编写规范。

为了测试系统,我想尽可能接近部署环境,所以我用 docker-compose 启动了我所有的系统,包括数据库、NodeJS 进程和消息队列。系统的所有组件都通过在 docker-compose 配置中定义的 docker 网络进行通信。

问题是我无法确保所有外部 API 都处于准备好接受我的请求的正确状态,并且它们会以我的测试步骤感兴趣的方式响应。

因此,我考虑为我的每个依赖项使用 Mock 服务器并发现 pact.io。据我所知,Pact 允许我编写合约并启动模拟服务器,这样我的系统就可以对模拟服务器发出 运行 HTTP 请求。 Pact 还允许我将合同交给服务提供商,这样它也可以 运行 与真实应用程序的合同,看看它是否真的有效。

我在 javascript 中看到了示例,我能够启动模拟服务,为其提供交互,验证交互并关闭模拟服务。 (JS with mocha example)

我的问题是我希望我的系统尽可能接近生产环境,因此我希望它通过我的 docker 网络访问 Pact 模拟服务。我看到了一个 Pact CLI docker 图像到 运行 pact 模拟服务 (Pact CLI docker image) but once my mock server is dockerized, I lose the control that I had with the JS wrapper to add new interactions.

此外,我不想编写 pact 文件,我想在我的测试 运行ning 时添加交互,否则我将声明测试数据两次(一次在黄瓜测试场景和在契约文件中一次)。

我的问题是:

有没有办法将 JS 包装器绑定到现有的模拟服务,dockerize 一个? 使用 docker 契约图像时,有没有办法在 运行 时间添加交互? 因为我只需要一个模拟服务,所以 pact 是正确的工具吗?

编辑

我只是创建了一个沙箱环​​境,看看可以用 NodeJS 包装器做什么。看来您可以使用 docker 创建一个模拟服务,然后他们通过 NodeJS 包装器控制它。

# Starts the docker container 

docker run -dit \
  --rm \
  --name pact-mock-service \
  -p 1234:1234 \
  -v <YOUR_PATH>/docker_pacts/:/tmp/pacts \
  pactfoundation/pact-cli:latest \
  mock-service \
  -p 1234 \
  --host 0.0.0.0 \
  --pact-dir /tmp/pacts
const {Pact, MockService, } = require('@pact-foundation/pact') 
const axios = require('axios')

const pact = new Pact({
  consumer: "my client",
  provider: "some provider",
  // Those two are ignored since we override the inner mock service
  port: 1234,
  host: 'localhost'
})


const mockService = new MockService(
  // You need to duplicate those data, normally they are passed
  // by the pact object when calling `pact.setup()`.
  'my client', 
  'provider', 
  // the port and host to the docker container
  1234, 
  'localhost'
)

pact.mockService = mockService

async function run () {
  await pact.addInteraction({
    state: "some data is created",
    withRequest: {
      method: "GET",
      path: "/hello"
    },
    willRespondWith: {
      status: 200,
      body: {
        hello: 'hello world'
      }
    },
    uponReceiving: ''
  })

  const response = await axios.get('http://localhost:1234/hello')

  console.log(response.data) // { "hello": "hello world" }
}

run().catch(console.error)

编辑 2

我可能会遵循 Matthew Fellows 的回答,并使用 Pact 模拟我系统的外部交互的某种单元测试来测试我的系统。

So, I thought about using a Mock server for each of my dependencies and discovered pact.io. As I understand, Pact allows me to write contracts and start a mock server so my system can then run HTTP requests against the mock server. Pact also allows me to give the contract to the service provider so it can also run the contract against the real app to see if it really works.

是的,没错。 Pact 可能被视为 API 客户端的单元测试工具,它使用契约测试来确保模拟有效。

如果您只是将 Pact 用于模拟,那么您将错过所有主要优势。

在如此高级别的测试中使用 Pact 被认为是不好的做法,正如您所见,这很难做到(您正在违背它的预期使用方式)。

我不太担心重叠测试(end-to-end 测试总是会与设计的其他层测试加倍),而更关心确保每个 API 契约都被 Pact 覆盖.这些测试将 运行 快得多,测试内容更精确并且不那么古怪。

这样,您可以将 end-to-end BDD 场景的范围缩小到关键场景,这应该会降低维护成本。