简体   繁体   English

如何使用 Jest 测试调用 API 的函数?

[英]How to test a function that calls an API using Jest?

I am new to Node.js unit testing with Jest and still learning.我是使用 Jest 进行 Node.js 单元测试的新手,并且仍在学习。 I was wondering what is the proper way of unit testing a function that calls an API?我想知道对调用 API 的函数进行单元测试的正确方法是什么? Currently I am using cross-fetch library for API calls.目前我正在使用交叉提取库进行 API 调用。 I would like to achieve the unit test for payload validation, 2xx and 5xx API response on API calls.我想实现有效载荷验证、API 调用上的 2xx 和 5xx API 响应的单元测试。

Here's my code:这是我的代码:

export const myFunction = (payload: any) => {
  if (_.isNull(payload) || _.isUndefined(payload)) {
    throw new Error('payload is required')
  }

  httpFetch('http://localhost/api/send', { method: 'POST' }, { 'content-type': 'application/json', Authorization: 'Bearer 12ABC'})
    .then((resp) => {
      // ...return 2xx
    })
    .catch((e) => {
       // ...return 5xx
    })
}

There are 2 approaches to doing this:有两种方法可以做到这一点:

Mock or fake the API call and output fake response (error or otherwise)模拟或伪造 API 调用并输出虚假响应(错误或其他)


httpFetch = jest.fn(()=>Promise.resolve("provide-dummy-response-payload"));

httpFetch = jest.fn(()=>Promise.reject("provide-dummy-error-payload"));

Now you can use the mock in a test like so:现在您可以在测试中使用模拟,如下所示:

// pseudo code

  it("makes the api call successfully",async ()=>{
     httpFetch = jest.fn(()=>Promise.resolve("provide-dummy-response-payload"));
     const result = await myFunction("random-payload");
     // make assertions about the result here
  });

  it("fails the api call",async ()=>{
     httpFetch = jest.fn(()=>Promise.reject("provide-dummy-error-payload"));
     const error = await myFunction("random-payload");
     // make assertions about error here
  });

(2) Make the api call by deliberately passing correct and incorrect payload and matching the expected results (2)通过故意传递正确和不正确的payload并匹配预期结果来进行api调用

In this case, you will need to know how to make the API call fail or pass.在这种情况下,您需要知道如何使 API 调用失败或通过。

So perhaps your API fails if payload doesn't contain a certain prop or if the prop is of incorrect type.因此,如果有效负载不包含某个道具或者道具的类型不正确,那么您的 API 可能会失败。

This approach is dependent on your payload which you provide to the function.这种方法取决于您提供给函数的有效负载。

Broadly there are two (not mutually exclusive) ways to unit test* a function like this:从广义上讲,有两种(非互斥的)方法可以对这样的函数进行单元测试*:

  1. Isolated test, with test doubles replacing the collaborators:隔离测试, 测试替身替换合作者:

     import httpFetch from "wherever"; import myFunction from "somewhere"; jest.mock("wherever"); describe("myFunction", () => { it("calls httpFetch", async () => { httpFetch.mockResolvedValue(); await myFunction({}); expect(httpFetch).toHaveBeenCalledWith( "http://localhost/api/send", { method: "POST" }, { "Content-Type": "application/json", Authorization: "Bearer 12ABC" } ); }); });

    This is the "easiest" way to do it, but now you're coupled to the httpFetch interface, which breaks the rule "don't mock what you don't own" - if that library's interface changes at some point, these tests won't tell you that.这是“最简单”的方法,但现在你耦合到了httpFetch接口,这打破了“不要嘲笑你不拥有的东西”的规则——如果该库的接口在某个时候发生变化,这些测试不会告诉你的。

  2. Integration test, checking what happens at the transport layer using something like Nock :集成测试,使用类似Nock 的东西检查传输层发生了什么:

     import nock from "nock"; import myFunction from "somewhere"; describe("myFunction", async () => { it("makes the right request", () => { const scope = nock("http://localhost/api", { reqheaders: { "Content-Type": "application/json", Authorization: "Bearer 12ABC", }, }) .post("/send") .reply(201); await myFunction({}); scope.done(); }); });

    This takes a bit more setup, but means you are less coupled to the httpFetch interface - you could upgrade that library or switch to a different one, for example, and still be confident things were working.这需要更多的设置,但意味着您与httpFetch接口的耦合较少 - 例如,您可以升级该库或切换到不同的库,并且仍然确信一切正常。

There are other ways to decouple from the specific library's interface;还有其他方法可以与特定库的接口解耦; you could write a facade around it and mock that instead, for example.例如,您可以围绕它编写一个外观并模拟它。 But you'd still want to know that the right request was being made, and you shouldn't test the facade against a test double of the library for the same reason as before.但是您仍然想知道正在发出正确的请求,并且出于与以前相同的原因,您不应该针对库的测试替身测试外观。

You may also have higher-level tests, eg E2E tests against the real backend or contract tests against a stub of it;您可能还有更高级别的测试,例如针对真实后端的 E2E 测试或针对其存根的合同测试; this will impact how you want to balance the number and type of your lower-level tests.这将影响您希望如何平衡较低级别测试的数量和类型。 Overall these options look something like:总的来说,这些选项看起来像:

System:      [Service] -> [Library] -> [HTTP] -> [Backend]

Isolated:    |<----->| -> (Test Double)

Integration: |<------------------>| -> (Nock)

Contract:    |<---------------------------->| -> (Stub)

E2E:         |<----------------------------------------->|

Remember that the goal (or one of them ) is to be confident that the code you're writing works and that if that stops being the case you'll find out promptly in a way that helps you fix it.请记住,目标(或其中之一)是确信您正在编写的代码有效,并且如果情况不再如此,您会以一种帮助您修复它的方式及时发现。

* There are lots of ideas around exactly what might comprise a "unit test". *关于“单元测试”的确切组成部分,有很多想法。 Given the principles of speed, independence and parallelisability, the definition I've used in this context is: a test that doesn't actually make a network request.考虑到速度、独立性和可并行性的原则,我在此上下文中使用的定义是:实际上并未发出网络请求的测试。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM