單元測試(Unit Test) 是蠻重要的一件事情,對於追求品質的工程師來說,早早點開這個科技樹,省得日後接大案寫爛code寫到懷疑人生。
Nestjs本身可以使用第三方測試模組外(jasmine、Jest),也有內建測試模組,加上Nestjs的架構是朝SOLID原則去發展,用Nestjs去開發的專案,耦合程度會較低,寫起單元測試也較容易些。
1.0 請安裝Jest
npm install --save-dev ts-jest @types/jest
1.1 根目錄新增jest.json並修改package.json,程式碼如下。
jest.json
{
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"json"
],
"transform": {
"^.+\\.tsx?$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "/src/.*\\.(test|spec).(ts|tsx|js)$",
"collectCoverageFrom" : ["src/**/*.{js,jsx,tsx,ts}", "!**/node_modules/**", "!**/vendor/**"],
"coverageReporters": ["json", "lcov"]
}
package.json
"scripts": {
"start": "node index.js",
"start:watch": "nodemon",
"prestart:prod": "tsc",
"start:prod": "node dist/server.js",
"test": "jest --config=jest.json"
},
1.2 在Users資料夾新增users.controller.spec.ts,程式碼如下。
cmd 指令:
cd src/modules/Users
src/modules/Users/users.controller.spec.ts
import { UsersController } from './users.controller';
import { UsersService } from './Services/users.service';
import { ProductsService } from '../Products/Services/products.service';
import { Request, Response, Next } from '@nestjs/common';
describe('UsersController', () => {
let usersController: UsersController;
let usersService: UsersService;
let productService: ProductsService;
beforeEach(() => {
usersService = new UsersService();
usersController = new UsersController(usersService, productService);
});
describe('runTest', () => {
it('should return an array of users', () => {
//預期回傳的資料
const result = [
{ "_id": 1, "_name": "Michael", "_age": 25 },
{ "_id": 2, "_name": "Mary", "_age": 27 }
];
//jest會監視usersService.(),並給予假資料 result
jest.spyOn(usersService, 'getAllUsers').mockImplementation(() => result);
usersController.getAllUsers(Request, Response, Next).then((data) => {
//預期data會跟result一樣
expect(data).toBe(result);
})
})
})
})
1.3 暫時修改一下UsersController,註解掉response動作,程式碼如下。
src/modules/Users/users.controller.ts
@Get('users')
@Roles('admin')
//使用Express的參數
async getAllUsers( @Request() req, @Response() res, @Next() next) {
//Promise 有then catch方法可以調用
await this.userService.getAllUsers()
.then((users) => {
//多種Http的Status可以使用
//res.status(HttpStatus.OK).json(users);
})
.catch((error) => {
console.error(error);
//res.status(HttpStatus.INTERNAL_SERVER_ERROR);
})
}
1.3 實際跑一次單元測試,console結果如下。
cmd指令:
npm run test
說明:看到綠色打勾真爽,此為測試結果與預期一樣。
1.4 Nestjs 有Testing class可以幫助我們做單元測試,我們來使用一下,程式碼如下:
src/modules/Users/users.controller.spec.ts
import { UsersController } from './users.controller';
import { UsersService } from './Services/users.service';
import { ProductsService } from '../Products/Services/products.service';
import { Request, Response, Next } from '@nestjs/common';
import { Test } from '@nestjs/testing';
describe('UsersController', () => {
let usersController: UsersController;
let usersService: UsersService;
let productService: ProductsService;
//使用Nest Testing class
beforeEach(async () => {
const module = await Test.createTestingModule({
controllers: [UsersController],
components: [UsersService, ProductsService],
}).compile();
usersService = module.get<UsersService>(UsersService);
usersController = module.get<UsersController>(UsersController);
});
describe('runTest', () => {
it('should return an array of users', () => {
//預期回傳的資料
const result = [
{ "_id": 1, "_name": "Michael", "_age": 25 },
{ "_id": 2, "_name": "Mary", "_age": 27 }
];
//jest會監視usersService.(),並給予假資料 result
jest.spyOn(usersService, 'getAllUsers').mockImplementation(() => result);
usersController.getAllUsers(Request, Response, Next).then((data) => {
//預期data會跟result一樣
expect(data).toBe(result);
})
})
})
})
1.5 看一下測試結果,console結果如下。
cmd指令:
npm test
說明:同1.3的運行結果。
E2E Testing,俗稱端對端測試,隨著專案日益龐大,可能已經有無數個Restful API,每一隻Restful API是否正常運作?光手動測試就累死人了,所以可以寫好E2E Testing,直接跑測試,是否正常運作一目了然。
2.0 持續使用Jest和Nestjs原生Testing class,但要另外安裝一下supertest,接著請在根目錄新增e2e資料夾,並在e2e資料夾底下新增jest-e2e.json、Users資料夾,然後在Users資料夾裡新增users.controller.e2e-spec.ts,程式碼如下。
cmd指令
npm install --save-dev supertest & mkdir e2e & cd e2e & mkdir Users
e2e/jest-e2e.json
{
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"json"
],
"transform": {
"^.+\\.tsx?$": "<rootDir>/../node_modules/ts-jest/preprocessor.js"
},
"testRegex": "/e2e/.*\\.(e2e-test|e2e-spec).(ts|tsx|js)$",
"collectCoverageFrom" : ["src/**/*.{js,jsx,tsx,ts}", "!**/node_modules/**", "!**/vendor/**"],
"coverageReporters": ["json", "lcov"]
}
package.json
"scripts": {
"start": "node index.js",
"start:watch": "nodemon",
"prestart:prod": "tsc",
"start:prod": "node dist/server.js",
"test": "jest",
"e2e": "jest --config=e2e/jest-e2e.json --forceExit"
},
e2e/Users/users.controller.e2e-spec.ts
import * as express from 'express';
import * as request from 'supertest';
import { Test } from '@nestjs/testing';
import { UsersModule } from '../../src/modules/Users/users.module';
import { UsersService } from '../../src/modules/Users/Services/users.service';
describe('Users', () => {
const server = express();
//預期資料,測試要用
const usersService = {
getAllUsers: () => [
{ "_id": 1, "_name": "Michael", "_age": 25 },
{ "_id": 2, "_name": "Mary", "_age": 27 }
]
};
//使用Testing class
beforeAll(async () => {
const module = await Test.createTestingModule({
modules: [UsersModule]
})
.overrideComponent(usersService).useValue(usersService)
.compile()
const app = module.createNestApplication(server);
await app.init();
});
//測試你的Restful API
it('/GET users',()=>{
return request(server)
.get('/users')
//狀態碼
.expect(200)
//檢驗是否符合預期資料
.expect(usersService.getAllUsers())
})
});
2.1 復原UsersController的getAllUsers(),做E2E測試需要有Response,程式碼如下。
src/modules/Users/users.controller.ts
@Get('users')
@Roles('admin')
//使用Express的參數
async getAllUsers( @Request() req, @Response() res, @Next() next) {
//Promise 有then catch方法可以調用
await this.userService.getAllUsers()
.then((users) => {
//多種Http的Status可以使用
res.status(HttpStatus.OK).json(users);
})
.catch((error) => {
console.error(error);
res.status(HttpStatus.INTERNAL_SERVER_ERROR);
})
}
2.2 實際跑一次E2E測試,結果如下。
cmd指令
npm run e2e
console畫面
E2E測試成功,綠色爽感十足^^。
程式碼都在github