【问题标题】:Mocking request & result to have smallest unit-test possible模拟请求和结果以进行最小的单元测试
【发布时间】:2022-01-14 06:28:45
【问题描述】:

我正在使用 Express 构建一个 back-for-front 应用程序。 它通过路由从前面专门调用,然后调用外部 API 来返回结果。这是逻辑的示例代码:

dashboard.route.ts

const router = Router();
const dashboardController = new DashboardController();

router.get("/distantCall", dashboardController.getDistantCall);

dashboard.controller.ts

import { Request, Response, NextFunction } from "express";
import DashboardService from "../services/dashboard.service";

export class DashboardController {
    async getDistantCall(req: Request, res: Response, next: NextFunction) {
        DashboardService.getDistantCalls()
            .then((result: any) => {
                res.status(200).send(result);
            }).catch((error: any) => {
                next(error);
            });
    }
}

dashboard.service.ts

import { DashboardApi } from './dashboard.api';

class DashboardService {
    public async getDistantCall() {
        return new Promise((resolve, reject) => {
            new DashboardApi().getDistantCall()
                .then((response: any) => {
                    resolve({
                        distantResponse: response.body
                    });
                })
                .catch((error) => {
                    reject(error);
                });
        });
    }

DashboardAPI 类进行外部 http 调用并返回一个承诺。对于这个示例,它返回一个简单的文本“distantSuccess”

对于我的测试,我可以很容易地编写集成测试

dashboard.routes.spec.ts

import chai from "chai";
import chaiHttp from "chai-http";
import { expect } from "chai";
chai.use(chaiHttp);

import createServer from "../../src/server";
const app = createServer();

describe("dashboard routes", function() {    
    it('nominal distant call', async () => {
        const res = await chai.request(app).get("/dashboard/distantCall");
        expect(res.status).to.eq(200);
        expect(res.body).to.be.a('object');
        expect(res.body).to.have.property('distantResponse');
        expect(res.body.distantResponse).to.eq('distantSuccess');
    });
});

我的问题是构建 unit 测试。据我了解,我应该只测试控制器或服务,并使用模拟和存根来模拟范围之外的元素。这是我做的两个测试:

dashboard.controller.spec.ts

import { Request, Response, NextFunction } from "express";
import chai from "chai";
import chaiHttp from "chai-http";
import { expect } from "chai";
import sinon from "sinon";
chai.use(chaiHttp);

import createServer from "../../src/server";
const app = createServer();
import { DashboardController } from "../../src/controllers/dashboard.controller";
const dashboardController = new DashboardController();
import DashboardService from "../../src/services/dashboard.service";

describe("dashboard routes with fake objects", function () {
    it("distant call by controller", async () => {
        const mockRequest: any = {
            headers: {},
            body: {},
        };
        const mockResponse: any = {
            body: { distantResponse: "About..." },
            text: "test",
            status: 200,
        };
        const mockNext: NextFunction = () => {};

        await dashboardController.getDistantCallSucces(mockRequest, mockResponse, mockNext);

        expect(mockResponse.status).to.eq(200);
        expect(mockResponse.body).to.be.a("object");
        expect(mockResponse.body).to.have.property("distantResponse");
        expect(mockResponse.body.distantResponse).to.eq("About...");
    });
});

describe("dashboard routes with stubs", function () {
    before(() => {
        sinon
            .stub(DashboardService, "getDistantCall")
            .yields({ distantResponse: "distantSuccess" });
        });

    it("distant call by controller", async () => {
        const mockRequest: any = {};
        const mockResponse: any = {};
        const mockNext: NextFunction = () => {};

        const res = await dashboardController.getDistantCall(mockRequest, mockResponse, mockNext);
        console.log(res);
    });
});

对于第一次测试,我显然不了解它的用途。我正在测试我刚刚创建的对象,甚至不知道是否调用了该服务。 我觉得我应该做一些更像第二次测试的事情,但我得到了这个错误: TypeError: getDistantCall 预期会产生,但没有通过回调。

【问题讨论】:

    标签: node.js express unit-testing chai sinon


    【解决方案1】:

    我终于找到了解决办法。

    我创建了两个单独的文件:一个用于集成测试,一个用于单元测试。我稍微调整了来自远程服务器的响应,它现在是 '来自远程服务器',而不是上一条消息中设置的 "distantResponse"。 在控制器和服务上,我还将 getDistantCall 更改为两个不同的函数 getDistantCallSuccesgetDistantCallError 以强制解析和拒绝集成测试。

    dashboard-integration.spec.ts

    import chai, { expect } from "chai";
    import chaiHttp from "chai-http";
    chai.use(chaiHttp);
    import sinon from "sinon";
    
    import app from "../src/app";
    import { DashboardAPI } from '../src/dashboard/dashboard.api';
    
    describe("Dashboard intagration tests", () => {    
        describe("Integration with server online", () => {
            it('Dashboard routes up', async () => {
                const res = await chai.request(app).get("/dashboard");
                expect(res.status).to.eq(200);
                expect(res.text).to.eq('dashboard');
            });
            it('full process with expected success', async () => {
                const res = await chai.request(app).get("/dashboard/distantCallSuccess").set("code", "12345");
                expect(res.status).to.eq(200);
                expect(res.body).to.be.a('object');
                expect(res.body).to.have.property('distantResponse');
                expect(res.body.distantResponse).to.eq('from distant server');
            });
            it('full process with expected error', async () => {
                const res = await chai.request(app).get("/dashboard/distantCallError");
                expect(res.status).to.eq(500);
            });
        });
    
        describe("Integration with mocked server", () => {
            beforeEach(() => {
                sinon.restore();
            });
            it('full process with expected resolve', async () => {
                const mockedResponse = {body: 'mocked'};
                sinon.stub(DashboardAPI.prototype, 'getDistantCallSuccess').resolves(mockedResponse);
                const res = await chai.request(app).get("/dashboard/distantCallSuccess").set("code", "12345");
                expect(res.status).to.eq(200);
                expect(res.body).to.be.a('object');
                expect(res.body).to.have.property('distantResponse');
                expect(res.body.distantResponse).to.eq('mocked');
            });
            it('full process with expected reject', async () => {
                sinon.stub(DashboardAPI.prototype, 'getDistantCallSuccess').rejects({mockedError: true});
                const res = await chai.request(app).get("/dashboard/distantCallSuccess").set("code", "12345");
                expect(res.status).to.eq(500);
            });
        });
    });
    

    dashboard-unit.spec.ts

    我不得不使用 node-mocks-http 来模拟请求和响应对象

    import chai, { expect } from "chai";
    import chaiHttp from "chai-http";
    import sinon from "sinon";
    import httpMocks from "node-mocks-http";
    chai.use(chaiHttp);
    
    import { DashboardController } from "../src/dashboard/dashboard.controller";
    import DashboardService from "../src/dashboard/dashboard.service";
    import { DashboardAPI } from '../src/dashboard/dashboard.api';
    
    describe("Unit Testing the Dashboard process", () => {
        describe("Unit Testing the controller", () => {
            const dashboardController = new DashboardController();
            beforeEach(() => {
                sinon.restore();
            });
            it("testing controller call without headers [catch]", async () => {
                var request = httpMocks.createRequest({});
                var response = httpMocks.createResponse();
                const next = () => {};
            
                await dashboardController.getDistantCallSuccess(request, response, next);
    
                expect(response._getStatusCode()).to.eq(500);
                expect(response._getData()).to.eq("Missing HEADER Parameter");
            });
            it("testing controller call with headers [resolve]", async () => {
                const mockedResponse = {
                    mockedResponse: true
                };
                sinon.stub(DashboardService, 'getDistantCallSuccess').resolves(mockedResponse);
                var request = httpMocks.createRequest({
                    headers: { code: "123" }
                });
                var response = httpMocks.createResponse();
                const next = () => {};
            
                await dashboardController.getDistantCallSuccess(request, response, next);
                expect(response._getStatusCode()).to.eq(200);
                expect(response._getData()).to.eql({mockedResponse: true});
            });
            it("testing controller call with headers [reject]", async () => {
                sinon.stub(DashboardService, 'getDistantCallSuccess').rejects({customError: true});
                
                const request = httpMocks.createRequest({});
                const response = httpMocks.createResponse();
                const next = (res) => {
                    expect(res).to.eql({customError: true});
                };
                await dashboardController.getDistantCallError(request, response, next);
            });
        });
        
        describe("Unit Testing the service", () => {
            beforeEach(() => {
                sinon.restore();
            });
            it("testing service call with resolve", async() => {
                const mockedResponse = {
                    body: 'mocked'
                };
                sinon.stub(DashboardAPI.prototype, 'getDistantCallSuccess').resolves(mockedResponse);
                
                let result;
                await DashboardService.getDistantCallSuccess().then(res => {
                    result = res;
                });
                expect(result).to.be.a('object');
                expect(result).to.be.haveOwnProperty('distantResponse');
                expect(result.distantResponse).to.eq('mocked');
            });
            it("testing service call with reject", async() => {
                sinon.stub(DashboardAPI.prototype, 'getDistantCallSuccess').rejects({mockedError: true});
                
                let result;
                await DashboardService.getDistantCallSuccess()
                    .then(res => {
                        result = res;
                    })
                    .catch(err => {
                        result = err;
                    });
                expect(result).to.eql({mockedError: true});
            });
        });
    });
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-06-03
      • 2020-04-06
      • 2020-07-07
      • 1970-01-01
      • 2013-03-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多