[英]Angular Dependency Injection - @Injectable() fails in test, while @Inject() works
我刚刚开始将角度为5的webpacker添加到现有的rails应用程序中。 一切正常,除了测试中有一个奇怪的DI问题。
似乎我的Angular组件仅在使用浏览器创建时才可以使用,但是在使用Jasmine / Karma进行测试时,Dependency Injector无法识别注入令牌。 使用伪代码:
@Component({...})
export class SomeComponent {
constructor(private service: SomeService) {}
}
上面的代码在浏览器中有效,但是给出了Error: Can't resolve all parameters for SomeComponent: (?).
在测试中。 到目前为止,我已经注意到它适用于所有@Injectable(),但是一旦我将每个注入都替换为显式@Inject:
@Component({...})
export class SomeComponent {
constructor(@Inject(SomeService) private service: SomeService) {}
}
一切正常(但显然很麻烦)。 有什么明显的原因可能导致这种情况吗?
我有一个使用HttpClient运行的非常简单的服务:
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import 'rxjs/add/operator/map'
@Injectable()
export class GeneralStatsService {
constructor(
private http : HttpClient
) {}
getMinDate() {
return this.http.get("/api/v1/general_stats/min_date")
.map(r => new Date(r))
}
}
当我导航到正在使用所述服务的组件时,它可以按预期工作。 但是,在使用Jasmine测试时,它不起作用:
import { TestBed } from "@angular/core/testing";
import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing";
import { GeneralStatsService } from "./general-stats.service";
describe('GeneralStatsService', () => {
let service : GeneralStatsService;
let httpMock : HttpTestingController;
beforeEach(()=> {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule
],
providers: [
GeneralStatsService
]
})
});
beforeEach(() => {
service = TestBed.get(GeneralStatsService);
httpMock = TestBed.get(HttpTestingController);
});
afterEach(() => {
httpMock.verify();
});
describe('getMinDate()', () => {
let fakeResponse : string = "2015-03-05T12:39:11.467Z";
it('returns instance of Date', (done) => {
service.getMinDate().subscribe((result : Date) => {
expect(result.getFullYear()).toBe(2015);
expect(result.getMonth()).toBe(2); // January is 0
expect(result.getDate()).toBe(5);
done();
});
const req = httpMock.expectOne("/api/v1/general_stats/min_date");
expect(req.request.method).toBe('GET');
req.flush(fakeResponse);
})
});
});
如上所述,添加显式@Inject(HttpClient)
修复测试,但我希望避免这种情况。
业力:
const webpackConfig = require('./config/webpack/test.js');
module.exports = function(config) {
config.set({
basePath: '',
frameworks: [ 'jasmine' ],
plugins: [
require('karma-webpack'),
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('karma-spec-reporter')
],
files: [
'config/webpack/angular-bundle.ts'
],
webpack: webpackConfig,
preprocessors: {
'config/webpack/angular-bundle.ts': ["webpack"]
},
mime: { "text/x-typescript": ["ts"]},
coverageIstanbulReporter: {
reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true
},
client: { clearContext: false },
reporters: [ 'progress', 'kjhtml', 'coverage-istanbul' ],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: [ 'Chrome' ],
singleRun: false,
concurrency: Infinity
})
};
config / webpack / test.js:
const environment = require('./environment');
environment.plugins.get('Manifest').opts.writeToFileEmit = process.env.NODE_ENV !== 'test';
environment.loaders.set('istanbul-instrumenter', {
test: /\.ts$/,
enforce: 'post',
loader: 'istanbul-instrumenter-loader',
query: {
esModules: true
},
exclude: ["node_modules", /\.spec.ts$/]
});
module.exports = environment.toWebpackConfig()
config / webpack / angular-bundle.ts:
import 'zone.js/dist/zone'
import 'zone.js/dist/long-stack-trace-zone';
import 'zone.js/dist/proxy.js';
import 'zone.js/dist/sync-test';
import 'zone.js/dist/jasmine-patch';
import 'zone.js/dist/async-test';
import 'zone.js/dist/fake-async-test';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: any;
jasmine.MAX_PRETTY_PRINT_DEPTH = 3;
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
const context = (require as any).context('../../app/javascript', true, /\.spec\.ts$/);
context.keys().map(context);
tsconfig.json:
{
"compilerOptions": {
"declaration": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": ["es6", "dom"],
"module": "es6",
"moduleResolution": "node",
"sourceMap": true,
"target": "es5"
},
"exclude": [
"**/*.spec.ts",
"node_modules",
"vendor",
"public",
"config/**/*.ts"
],
"compileOnSave": false
}
environment.js:
const environment = require('@rails/webpacker').environment;
const typescript = require('./loaders/typescript');
const erb = require('./loaders/erb');
const elm = require('./loaders/elm');
const html = require('./loaders/html');
environment.loaders.append('elm', elm);
environment.loaders.append('erb', erb);
environment.loaders.append('typescript', typescript);
environment.loaders.append('html', html);
module.exports = environment;
以防万一装载机/打字稿:
module.exports = {
test: /\.(ts|tsx)?(\.erb)?$/,
use: [{
loader: 'ts-loader'
}]
}
因此,查看使用@Inject
生成的JavaScript和仅使用@Component
或@Injectable
生成的JavaScript(从完整的装饰器中提取):
__param(0, core_1.Inject(http_1.HttpClient)), // via @Inject
__metadata("design:paramtypes", [http_1.HttpClient]) // with @Component, @Injectable only
这来自Angular 5的最新版本,但可能一直应用到2。您可以看到@Inject
生成显式参数注入,否则注入仅依赖元数据。 正如您所建议的那样,这似乎强烈表明您的问题与emitDecoratorMetadata
标志有关。
由于emitDecoratorMetadata
不是默认启用的选项,因此您的tsconfig.json
可能未包含在构建中。 您可以使用ts-loader
configFile
属性显式指定其位置:
use: [{
loader: 'ts-loader',
options: {
configFile: 'tsconfig.json' // default
}
}]
如文档所述,指定文件名与相对路径不同。 对于文件名, ts-node
将遍历试图找到该文件的文件夹树,但是对于相对路径,它将仅相对于您的输入文件尝试。 您还可以指定一个绝对路径(对于诊断,只需删除硬编码的路径可能会有用)。
如果失败,我可能还会通读Angular Webpack指南 ,其中详细介绍了awesome-typescript-loader
用法(是的,在我相信它是真实的之前,我必须先进行查找...),而不是ts-loader
。 它还使用帮助程序来生成绝对路径,从而显式定义tsconfig
路径。
尝试使用进样器和spyOn。
您必须创建一个没有'HttpClient'的模拟服务,该服务具有要模拟的服务的所有方法。 然后,使用spyOn可以返回您想要的内容。
TestBed.configureTestingModule({ imports: [ FormsModule, BrowserAnimationsModule ], providers: [ { provide: YourService, useValue: mockedYourService } ] .... beforeEach(() => { fixture = TestBed.createComponent(YourTestingComponent); component = fixture.componentInstance; element = fixture.nativeElement; fixture.detectChanges(); }); ... describe('methodName', () => { it('message to print', () => { const your_Service = fixture.debugElement.injector.get(YourService); spyOn(your_Service, 'methodName').and.returnValue(true); .....
希望有帮助!
您是否尝试过在测试平台配置中将HttpClient添加为提供程序?
TestBed
.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [GeneralStatsService,
{ provide: HttpClient, useValue: new HttpClient() }
]
})
这是有人遇到类似问题时,业力开发人员提出的一个建议。 当您要测试具有依赖项的组件时,Angular团队也建议这样做 。
问题是您的spec.ts
文件被排除在tsconfig.json
,从而使emitDecoratorMetadata
不将emitDecoratorMetadata
应用于您的规范吗?
我遇到了类似的问题,我通过在polyfills.js文件中导入core-js来解决它。 但我仍然不知道为什么会这样。
import 'core-js';
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.