如何使用包含Joi的jest测试expressJS端点进行请求验证?

How to test a expressJS endpoint using jest which includes Joi for request validation?

提问人:Abdul-Raffay 提问时间:9/18/2023 更新时间:9/18/2023 访问量:27

问:

我一直在开发一个 expressJS 应用程序,其中我编写了一个端点,该端点向用户发送调用以进行 OTP 验证,以下是端点函数的定义:

  async sendVoiceCall(req, res, next) {
    try {
      const validationSchema = Joi.object({
        phoneNo: Joi.string().required(),
      });
      const { error } = validationSchema.validate(req.body);
      if (error) {
        console.log("Error occured.....");
        return next(error);
      }
      const { phoneNo } = req.body;
      const select =
        "-connectionsCount -competitionsCount -winsCount -audienceCount -prizesGeneratedCount -blackDiamonds -createdAt -updatedAt";
      const user = await User.findOne({ phoneNo: phoneNo }).select(select);
      if (!user) {
        throw new CustomErrorHandler(
          404,
          "User with the provided phone does not exists"
        );
      }
      // generate the OTP and save that with its expiry in the dB
      const otp = functions.generateOTP();
      await User.findOneAndUpdate(
        { _id: user._id },
        {
          otp,
          otpDateTime: moment().add(5, "minute"),
        },
        { new: true }
      );
      const message = `Your OTP code is: ${otp}`;
      await sendVoiceCall(phoneNo, message);
      res.status(200);
      res.json({ message: "Call has initiated successfully" });
    } catch (error) {
      return next(error);
    }
  },

这里的 sendVoice() 函数实际上是使用 AWS Pinpoint 服务向用户发送 OTP 调用。

我正在尝试使用开玩笑来测试这个函数,这是我到目前为止所做的:

// Import necessary modules and dependencies
import sendVoiceCall from "../../../../utils/sendVoiceCall";
import User from "../../../../model/user";
import functions from "../../../../utils/functions";
import Joi from "joi";
import UsersController from "../UsersController";

jest.mock("../../../../services/CustomErrorhandler");
jest.mock("../../../../model/user");
jest.mock("../../../../utils/functions");
jest.mock("../../../../utils/sendVoiceCall");
jest.mock("joi", () => ({
  object: jest.fn((x) => x),
  validate: jest.fn((x) => x),
  string: jest.fn((x) => x),
}));

describe("sendVoiceCall", () => {
  it("should initiate a voice call with a valid phone number", async () => {
    // Mock request and response object
    const req = {
      body: {
        phoneNo: "+923132290956",
      },
    };
    const res = {
      status: jest.fn((x) => x),
      json: jest.fn((x) => x),
    };
    const next = jest.fn();

    // Mock User.findOne to return a user
    // Mock the findOne method and return a query builder object
    User.findOne = jest.fn().mockReturnThis();

    // Mock the select method on the query builder
    User.findOne.mockReturnValueOnce({
      select: jest.fn().mockResolvedValue({
        _id: "user_id",
      }),
    });

    // Mock functions.generateOTP to return a fixed OTP value
    functions.generateOTP.mockReturnValue("123456");

    await User.findOneAndUpdate.mockResolvedValue({
      _id: "user_id",
    });

    // Mock the sendVoiceCall utility function
    sendVoiceCall.mockResolvedValue("Call initiated successfully");

    const schema = Joi.object({
      key: Joi.string(),
    });
    const value = {
      key: "3",
    };

    // Call the function
    await UsersController.sendVoiceCall(req, res, next);

    // Assertions
    expect(res.status).toHaveBeenCalledWith(200);
    expect(res.json).toHaveBeenCalledWith({
      message: "Call has initiated successfully",
    });
  });

  it("should handle errors when initiating a voice call", async () => {
    // Mock dependencies and request object
    const req = {
      body: {
        phoneNo: "+1234567890", // Replace with a valid phone number
      },
    };
    const res = {
      status: jest.fn().mockReturnThis(),
      json: jest.fn(),
    };
    const next = jest.fn();

    // Mock validation schema to return no error
    Joi.object().mockReturnValue({
      validate: jest.fn().mockReturnValue({ error: null }),
    });

    // Mock User.findOne to return no user (user not found)
    User.findOne.mockResolvedValue(null);

    // Call the function
    await sendVoiceCall(req, res, next);

    // Assertions
    expect(next).toHaveBeenCalledWith(
      new CustomErrorHandler(404, "User with the provided phone does not exist")
    );
  });

  it("should handle validation errors", async () => {
    // Mock dependencies and request object
    const req = {
      body: {
        phoneNo: "", // An empty phone number (invalid)
      },
    };
    const res = {
      status: jest.fn().mockReturnThis(),
      json: jest.fn(),
    };
    const next = jest.fn();

    // Mock validation schema to return an error
    Joi.object().mockReturnValue({
      validate: jest
        .fn()
        .mockReturnValue({ error: new Error("Validation error") }),
    });

    // Call the function
    await sendVoiceCall(req, res, next);

    // Assertions
    expect(next).toHaveBeenCalledWith(new Error("Validation error"));
  });
});

我已经尝试了三个测试用例,但是当我运行此测试文件时,我的所有测试用例都失败了,并出现以下错误:

  ✕ should initiate a voice call with a valid phone number (15 ms)
    ✕ should handle errors when initiating a voice call (2 ms)
    ✕ should handle validation errors (1 ms)

  ● sendVoiceCall › should initiate a voice call with a valid phone number

    expect(jest.fn()).toHaveBeenCalledWith(...expected)

    Expected: 200

    Number of calls: 0

      62 |
      63 |     // Assertions
    > 64 |     expect(res.status).toHaveBeenCalledWith(200);
         |                        ^
      65 |     expect(res.json).toHaveBeenCalledWith({
      66 |       message: "Call has initiated successfully",
      67 |     });


  ● sendVoiceCall › should handle errors when initiating a voice call

    TypeError: Cannot read properties of undefined (reading 'mockReturnValue')

      82 |
      83 |     // Mock validation schema to return no error
    > 84 |     Joi.object().mockReturnValue({
         |                 ^
      85 |       validate: jest.fn().mockReturnValue({ error: null }),
      86 |     });


  ● sendVoiceCall › should handle validation errors

    TypeError: Cannot read properties of undefined (reading 'mockReturnValue')

      112 |
      113 |     // Mock validation schema to return an error
    > 114 |     Joi.object().mockReturnValue({
          |                 ^
      115 |       validate: jest
      116 |         .fn()
      117 |         .mockReturnValue({ error: new Error("Validation error") }),


Test Suites: 1 failed, 1 total
Tests:       3 failed, 3 total
Snapshots:   0 total
Time:        4.932 s, estimated 6 s



请告诉我如何使用 jest 为我的函数 sendVoiceCall 编写单元测试,包括 Joi 验证。

节点 .js Express 单元测试 jestjs joi

评论

0赞 jonrsharpe 9/18/2023
不要对控制器进行单元测试 - 不要嘲笑你不拥有的东西,比如 Express 的 API。这与传输层耦合得太厉害,无法隔离。在集成级别进行测试,用请求和响应术语描述它,例如 Supertest。
0赞 Abdul-Raffay 9/18/2023
你能给我更多关于如何在这么高的水平上进行测试的解释吗?
0赞 jonrsharpe 9/18/2023
blog.jonrshar.pe/2023/May/23/js-tdd-ohm.html

答: 暂无答案