В настоящее время я пишу тесты для проекта TSED (express), и всякий раз, когда я вызываю службу, используя внедрение зависимостей в один файл, я получаю сообщение об ошибке, что служба не определена в совершенно другой части проекта. Помните, что весь код во всех других не тестовых файлах работает правильно.
DBConnectorService.ts обрабатывает все запросы к БД.
import { Service, Scope, ProviderScope } from "@tsed/di";
import { Pool } from "pg";
import { GLOBAL_DB_POOL } from "../server";
@Service()
export class DBConnectorService {
private pool: Pool;
constructor() {
this.pool = GLOBAL_DB_POOL;
}
executeQuery(query) {
return new Promise(resolve => {
this.pool.query(query, (err, results) => {
if (err) {
throw err;
}
resolve(results.rows);
});
});
}
}
DBConnectorService .spe c .ts обрабатывает тесты для указанного выше файла.
import { DBConnectorService } from "./../../../src/services";
import { inject } from '@tsed/testing';
import { expect } from 'chai';
describe('DBConnectorService', () => {
it('should be an instance', () => {
const dbConnectorService = new DBConnectorService();
expect(dbConnectorService).to.be.an.instanceof(DBConnectorService);
});
});
Когда этот тест запущен, вывод:
❯ npm run test:unit
DBConnectorService
✓ should be an instance
1 passing (4ms)
Это здорово, это работает как надо. Однако, когда я изменяю способ реализации службы в тесте с экземпляра на инъекцию, например, так:
import { DBConnectorService } from "./../../../src/services";
import { inject } from '@tsed/testing';
import { expect } from 'chai';
describe('DBConnectorService', () => {
it('should be an instance', inject([DBConnectorService], (dbConnectorService: DBConnectorService) => {
expect(dbConnectorService).to.be.an.instanceof(DBConnectorService);
}));
});
и снова запускаю тест, я получаю следующую ошибку:
❯ npm run test:unit
1 failing (31ms)
1) DBConnectorService
should be an instance:
INJECTION_ERROR: Injection failed on FormatCtrl
Origin: Unable to inject dependency. Given token is undefined. Have you enabled emitDecoratorMetadata in your tsconfig.json or decorated your class with @Injectable, @Service, ... decorator ?
FormatCtrl->constructor(formatService: undefined)
^‾‾‾‾‾‾‾‾‾‾‾‾
at Function.throwInjectorError (node_modules/@tsed/di/src/errors/InjectionError.ts:43:11)
at Map.resolve (node_modules/@tsed/di/src/services/InjectorService.ts:526:22)
at Map.invoke (node_modules/@tsed/di/src/services/InjectorService.ts:194:38)
at Map.loadSync (node_modules/@tsed/di/src/services/InjectorService.ts:249:16)
at Map.<anonymous> (node_modules/@tsed/di/src/services/InjectorService.ts:277:19)
at Generator.next (<anonymous>)
at fulfilled (node_modules/tslib/tslib.js:107:62)
Это не имеет никакого смысла, потому что файл FormatCtrl.ts :
import { Controller, Get } from "@tsed/common";
import { FormatService } from "../services";
@Controller('/formats')
export class FormatCtrl {
constructor(private formatService: FormatService) { }
@Get('/')
findAll() {
return new Promise(resolve => {
this.formatService.findAll().then(resolve);
});
}
}
использует FormatService.ts :
import { Service } from '@tsed/di';
import { DBConnectorService } from './DBConnectorService';
@Service()
export class FormatService {
constructor(private dbConnectorService: DBConnectorService) { }
findAll() {
return new Promise(resolve => {
this.dbConnectorService.executeQuery('SELECT * FROM formats').then(resolve);
});
}
}
, который просто использует DBConnectorService
Что-то еще более странное в том, что если я дважды определю один и тот же тест, то это произойдет:
DBConnectorService.spe c .ts
import { DBConnectorService } from "./../../../src/services";
import { inject } from '@tsed/testing';
import { expect } from 'chai';
describe('DBConnectorService', () => {
it('should be an instance 1', inject([DBConnectorService], (dbConnectorService: DBConnectorService) => {
expect(dbConnectorService).to.be.an.instanceof(DBConnectorService);
}));
it('should be an instance 2', inject([DBConnectorService], (dbConnectorService: DBConnectorService) => {
expect(dbConnectorService).to.be.an.instanceof(DBConnectorService);
}));
});
вывод:
❯ npm run test:unit
DBConnectorService
1) should be an instance 1
✓ should be an instance 2
1 passing (32ms)
1 failing
1) DBConnectorService
should be an instance 1:
INJECTION_ERROR: Injection failed on FormatCtrl
Origin: Unable to inject dependency. Given token is undefined. Have you enabled emitDecoratorMetadata in your tsconfig.json or decorated your class with @Injectable, @Service, ... decorator ?
FormatCtrl->constructor(formatService: undefined)
^‾‾‾‾‾‾‾‾‾‾‾‾
at Function.throwInjectorError (node_modules/@tsed/di/src/errors/InjectionError.ts:43:11)
at Map.resolve (node_modules/@tsed/di/src/services/InjectorService.ts:526:22)
at Map.invoke (node_modules/@tsed/di/src/services/InjectorService.ts:194:38)
at Map.loadSync (node_modules/@tsed/di/src/services/InjectorService.ts:249:16)
at Map.<anonymous> (node_modules/@tsed/di/src/services/InjectorService.ts:277:19)
at Generator.next (<anonymous>)
at fulfilled (node_modules/tslib/tslib.js:107:62)
Так что, когда я впервые пытаюсь использовать инъекцию, она терпит неудачу, но потом, если я использую ее снова в том же файле, она волшебным образом работает. Это работает, даже если я выполняю тот же самый тест в третий раз. Кто-нибудь знает, что здесь происходит?
Вот мои конфигурационные файлы
пакет. json
{
"name": "back-end",
"version": "1.0.0",
"description": "",
"main": "src/index.js",
"scripts": {
"clean": "rimraf '{src,test}/**/*.{js,js.map}'",
"build": "yarn tsc",
"test": "yarn clean && yarn test:lint && yarn test:coverage",
"test:unit": "cross-env NODE_ENV=test mocha",
"test:coverage": "cross-env NODE_ENV=test nyc mocha",
"test:lint": "tslint --project tsconfig.json",
"test:lint:fix": "tslint --project tsconfig.json --fix",
"travis:deploy-once": "travis-deploy-once",
"travis:coveralls": "nyc report --reporter=text-lcov | coveralls",
"tsc": "tsc --project tsconfig.json",
"tsc:w": "tsc --project tsconfig.json -w",
"start": "nodemon --watch \"src/**/*.ts\" --ignore \"node_modules/**/*\" --exec ts-node src/index.ts",
"start:prod": "cross-env NODE_ENV=production node dist/index.js",
"docker:build": "yarn build && docker-compose build",
"deploy": "exit 0"
},
"author": "",
"license": "ISC",
"dependencies": {
"@tsed/common": "5.42.1",
"@tsed/core": "5.42.1",
"@tsed/di": "5.42.1",
"@tsed/multipartfiles": "^5.42.1",
"@tsed/swagger": "5.42.1",
"@tsed/testing": "5.42.1",
"body-parser": "1.19.0",
"compression": "1.7.4",
"concurrently": "5.0.0",
"cookie-parser": "1.4.4",
"cors": "2.8.5",
"cross-env": "6.0.3",
"express": "4.17.1",
"fluent-ffmpeg": "^2.1.2",
"jimp": "^0.9.3",
"method-override": "^3.0.0",
"node-uuid": "1.4.8",
"pg": "^7.18.1"
},
"devDependencies": {
"@types/chai": "4.2.5",
"@types/chai-as-promised": "7.1.2",
"@types/cors": "2.8.6",
"@types/express": "4.17.2",
"@types/fluent-ffmpeg": "^2.1.14",
"@types/http-proxy": "1.17.2",
"@types/mocha": "5.2.7",
"@types/multer": "^1.4.2",
"@types/node": "12.12.9",
"@types/pg": "^7.14.1",
"@types/request-promise": "4.1.45",
"@types/sinon": "7.5.0",
"@types/sinon-chai": "3.2.3",
"@types/supertest": "2.0.8",
"@types/swagger-schema-official": "^2.0.20",
"@types/uuid": "^3.4.7",
"chai": "4.2.0",
"chai-as-promised": "7.1.1",
"concurrently": "5.0.0",
"mocha": "6.2.2",
"nodemon": "1.19.4",
"nyc": "14.1.1",
"rimraf": "3.0.0",
"sinon": "7.5.0",
"sinon-chai": "3.3.0",
"supertest": "4.0.2",
"ts-node": "^8.6.2",
"tsconfig-paths": "^3.9.0",
"tslint": "5.20.1",
"typescript": "3.7.5"
}
}
мокко. opts
--require node_modules/ts-node/register
{src,test}/**/*.spec.ts
tsconfig. json
{
"compilerOptions": {
"baseUrl": ".",
"outDir": "./dist",
"target": "ES2016",
"lib": [
"ES2016",
"DOM"
],
"typeRoots": [
"./node_modules/@types"
],
"module": "CommonJS",
"moduleResolution": "node",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"sourceMap": true,
"declaration": false,
"allowSyntheticDefaultImports": true,
"noImplicitAny": false,
"isolatedModules": false
},
"include": [
"./src/**/*.ts"
],
"exclude": [
"./public",
"dist"
]
}
Также следует отметить, что я создал проект, выполнив следующие действия: https://tsed.io/getting-started.html