#node.js #express #unit-testing #chai #sinon
#node.js #экспресс #модульное тестирование #чай #sinon
Вопрос:
я создаю приложения для обратной связи с помощью Express. Он вызывается исключительно спереди по маршрутам, затем вызывает внешний API, чтобы вернуть результат. Вот пример кода логики :
панель мониторинга.маршрут.ts
const router = Router(); const dashboardController = new DashboardController(); router.get("/distantCall", dashboardController.getDistantCall);
панель управления.контроллер.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) =gt; { res.status(200).send(result); }).catch((error: any) =gt; { next(error); }); } }
dashboard.service.ts
import { DashboardApi } from './dashboard.api'; class DashboardService { public async getDistantCall() { return new Promise((resolve, reject) =gt; { new DashboardApi().getDistantCall() .then((response: any) =gt; { resolve({ distantResponse: response.body }); }) .catch((error) =gt; { reject(error); }); }); }
Класс DashboardAPI выполняет внешний http-вызов и возвращает обещание. Для этого примера он возвращает простой текст «distantSuccess».
Для своих тестов я довольно легко могу писать интеграционные тесты
панель мониторинга.маршруты.спецификации.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 () =gt; { 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'); }); });
My problem is building unit tests. As I understand it, I should only test the controller or the service, and using mocks amp; stubs to simulate the elements outside of the scope. Here are the two tests I made :
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 () =gt; { const mockRequest: any = { headers: {}, body: {}, }; const mockResponse: any = { body: { distantResponse: "About..." }, text: "test", status: 200, }; const mockNext: NextFunction = () =gt; {}; 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(() =gt; { sinon .stub(DashboardService, "getDistantCall") .yields({ distantResponse: "distantSuccess" }); }); it("distant call by controller", async () =gt; { const mockRequest: any = {}; const mockResponse: any = {}; const mockNext: NextFunction = () =gt; {}; const res = await dashboardController.getDistantCall(mockRequest, mockResponse, mockNext); console.log(res); }); });
Для первого теста я явно не понимаю, как его использовать. я тестирую только что созданный объект, даже не зная, вызывается ли служба. Я чувствую, что должен сделать что-то более похожее на второй тест, но я получаю эту ошибку : Ошибка типа: ожидалось, что getDistantCall даст результат, но обратный вызов не был передан.
Ответ №1:
Я наконец нашел решение.
Я создал два отдельных файла : один для интеграционного тестирования, другой для модульного тестирования. Я немного изменил ответ с удаленного сервера, который теперь «с удаленного сервера» вместо «distantResponse», установленного в предыдущем сообщении. В контроллере и службе я также изменил getDistantCall на две разные функции getDistantCallSucces и getDistantCallError, чтобы принудительно разрешить и отклонить интеграционные тесты.
панель мониторинга-интеграция.спецификация.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", () =gt; { describe("Integration with server online", () =gt; { it('Dashboard routes up', async () =gt; { 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 () =gt; { 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 () =gt; { const res = await chai.request(app).get("/dashboard/distantCallError"); expect(res.status).to.eq(500); }); }); describe("Integration with mocked server", () =gt; { beforeEach(() =gt; { sinon.restore(); }); it('full process with expected resolve', async () =gt; { 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 () =gt; { 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); }); }); });
приборная панель-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", () =gt; { describe("Unit Testing the controller", () =gt; { const dashboardController = new DashboardController(); beforeEach(() =gt; { sinon.restore(); }); it("testing controller call without headers [catch]", async () =gt; { var request = httpMocks.createRequest({}); var response = httpMocks.createResponse(); const next = () =gt; {}; 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 () =gt; { const mockedResponse = { mockedResponse: true }; sinon.stub(DashboardService, 'getDistantCallSuccess').resolves(mockedResponse); var request = httpMocks.createRequest({ headers: { code: "123" } }); var response = httpMocks.createResponse(); const next = () =gt; {}; 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 () =gt; { sinon.stub(DashboardService, 'getDistantCallSuccess').rejects({customError: true}); const request = httpMocks.createRequest({}); const response = httpMocks.createResponse(); const next = (res) =gt; { expect(res).to.eql({customError: true}); }; await dashboardController.getDistantCallError(request, response, next); }); }); describe("Unit Testing the service", () =gt; { beforeEach(() =gt; { sinon.restore(); }); it("testing service call with resolve", async() =gt; { const mockedResponse = { body: 'mocked' }; sinon.stub(DashboardAPI.prototype, 'getDistantCallSuccess').resolves(mockedResponse); let result; await DashboardService.getDistantCallSuccess().then(res =gt; { 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() =gt; { sinon.stub(DashboardAPI.prototype, 'getDistantCallSuccess').rejects({mockedError: true}); let result; await DashboardService.getDistantCallSuccess() .then(res =gt; { result = res; }) .catch(err =gt; { result = err; }); expect(result).to.eql({mockedError: true}); }); }); });