模拟请求和结果以尽可能进行最小的单元测试
Mocking request & result to have smallest unit-test possible
我正在使用 Express 构建后台应用程序。
它专门通过路由从前端调用,然后调用外部 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);
});
});
}
仪表板API class 进行外部 http 调用和 returns 承诺。对于此示例,它 returns 一个简单的文本“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 预期产生,但没有传递回调。
我终于找到了解决方案。
我创建了两个单独的文件:一个用于集成测试,一个用于单元测试。我稍微调整了来自远程服务器的响应,现在是 'from distant server' 而不是上一条消息中设置的 "distantResponse" .
在控制器和服务上,我还将 getDistantCall 更改为两个不同的函数 getDistantCallSucces 和 getDistantCallError 以强制解决并拒绝集成测试。
仪表板-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);
});
});
});
仪表板-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});
});
});
});
我正在使用 Express 构建后台应用程序。 它专门通过路由从前端调用,然后调用外部 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);
});
});
}
仪表板API class 进行外部 http 调用和 returns 承诺。对于此示例,它 returns 一个简单的文本“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 预期产生,但没有传递回调。
我终于找到了解决方案。
我创建了两个单独的文件:一个用于集成测试,一个用于单元测试。我稍微调整了来自远程服务器的响应,现在是 'from distant server' 而不是上一条消息中设置的 "distantResponse" . 在控制器和服务上,我还将 getDistantCall 更改为两个不同的函数 getDistantCallSucces 和 getDistantCallError 以强制解决并拒绝集成测试。
仪表板-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);
});
});
});
仪表板-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});
});
});
});