Ngrx测试-如何配置TestBed以便在测试中实例化整个状态存储 [英] Ngrx testing - How to configure TestBed in order to instantiate the entire state store in a test
问题描述
我想在Angular 5应用中为ngrx 4状态存储运行一些集成测试.我的愿望是一次一起测试调度ngrx动作,效果,webapi,模拟服务器和选择器.为效果和还原剂编写单独的测试将是一个耗时的工作,我需要努力一些.我只需要知道我的页面之一何时出现故障,而不是确切的位置.
I want to run some integration tests for the ngrx 4 state store in an Angular 5 app. My desire is to test dispatching ngrx actions, effects, webapi, mock server and selectors all together in one shot. Writting separate tests for effects and reducers is going to be a time sink and I need to cut some corners. I only need to know when one of my pages fails not the exact spot.
我在上一个项目中设法将Redux与Angular 2一起使用时做到了这一点.这很容易设置.但是,使用ngrx会遇到一些麻烦.运行TestBed
时,以某种方式无法正确地将AccountsWebapi
注入到AccountsEffects
类中.而不是接收webapi的实例,似乎我正在获取构造函数.同时,似乎已在_AccountsService
中正确注入并实例化了相同的webapi.
I managed to do this in a previous project while using redux together with Angular 2. That was quite easy to setup. However, with ngrx I have some trouble. Somehow AccountsWebapi
is not properly injected in AccountsEffects
class when running the TestBed
. Instead of receiving an instance of the webapi, it looks like I'm getting the constructor. At the same time, the same webapi seems to be injected and instantiated properly in _AccountsService
.
accounts.service.spec.ts
describe("AccountsService - ", () => {
let accService: AccountsService;
beforeAll( ()=> {
TestBed.resetTestEnvironment();
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
});
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
StoreModule.forRoot({appReducer}),
EffectsModule.forRoot([AccountsEffects]),
],
declarations: [],
providers: [
{ provide: AccountsService, useValue: AccountsService },
{ provide: AccountsWebapi, useValue: AccountsWebapi },
// ... Other dependencies
]
}).compileComponents();
// Instantiation
let _AccountsUtilsService = TestBed.get(AccountsUtilsService);
let _store = TestBed.get(Store);
let _AccountsService = TestBed.get(AccountsService);
// ... Other dependencies
accService = new _AccountsService(
new _AccountsUtilsService(),
new _AccountsWebapi(),
_store,
// ... Other dependencies
);
}));
it("Sample Test", () => {
console.log(`Accounts SERVICE`, accService.accountsWebapi);
accService.getAccountAsync(1);
expect(1).toEqual(1);
});
accounts.effects.ts
@Injectable()
export class AccountsEffects {
constructor(
private actions$: Actions,
private store$: Store<AppState>,
private accountsService: AccountsService,
private accountsWebapi: AccountsWebapi,
) {
console.log('Accounts EFFECTS', this.accountsWebapi)
// Typing this line will instantiate the dependency...
this.accountsWebapi = new (this.accountsWebapi as any)()
}
控制台日志
推荐答案
首次尝试.不要这样!阅读下面的
最后,我找到了一种运行这种测试的方法.我100%肯定有更好的方法来做到这一点.在我找到它之前,这是必须要做的.
Finally, I found a way to get this kind of test running. I am 100% sure there is a far better way to do it. Until I find it, this has to do.
/** Force initialise the web api in effects */
@Injectable()
class AccountsEffectsTest extends AccountsEffects {
constructor(
protected _actions$: Actions,
protected _store$: Store<AppState>,
protected _accountsWebapi: AccountsWebapi,
) {
super(
_actions$,
_store$,
_accountsWebapi,
);
// This line looks ugly but it spares a lot of imports
// Certainly there is a better way then this
let providers = ((new HttpModule()) as any).__proto__.constructor.decorators['0'].args['0'].providers;
let injector = ReflectiveInjector.resolveAndCreate([
...providers
]);
this.accountsWebapi = new (_accountsWebapi as any)(
null,
injector.get(Http)
);
console.log('Accounts EFFECTS', this.accountsWebapi);
}
}
最后,我可以得到能够运行实际请求的效果的webapi实例.现在,我要做的就是模拟一个webapi.我知道这与事实不符.我已经就什么是最佳测试方法进行了相当多的研究.我仍然发现对于我正在构建的那种应用程序,运行集成测试比单元测试更合适.
Finally, I can get an instance of the webapi in the effects capable of running real requests. Now all I have to do is to mock a webapi. I know it goes against the grain. I've done my fair share of research on what is the best way to test. I still find it more suitable to run integration tests rather than unit tests for the kind of app I'm building.
编辑(正确答案) 我将保留前面的答案,只是为了证明我愿意忽略代码的气味...
Edit (the proper answer) I will keep the previous answer just to demonstrate how willing I was to ignore a code smell...
由于对如何配置TestBed有一些误解,因此提供了AccountsWebapi
构造函数而不是实例.那是因为我正在使用{ provider: AccountsWebapi, useValue: AccountsWebapi }
.正确的设置是使用useClass
,如此处{ provider: AccountsWebapi, useClass: AccountsWebapi }
所示.使用类时,依赖项注入框架将触发构造步骤,并且只要正确配置了依赖项,它将注入适当的依赖项.
Due to some misunderstanding about how to configure the TestBed the AccountsWebapi
constructor was delivered instead of the instance. That was because I was using { provider: AccountsWebapi, useValue: AccountsWebapi }
. The proper setup is to make use of useClass
as demonstrated here { provider: AccountsWebapi, useClass: AccountsWebapi }
. When using a class the dependency injection framework will trigger the construction step and it will inject the proper dependencies as longs as they are configured properly.
另一个问题是AccountsWebapi
中未提供AuthHttp
服务.通过在testBed导入中将HttpModule
声明为依赖项,可以轻松解决此问题.另外,这对我来说也是一个很好的机会,让我了解到我需要在TestBed中一起提供几个紧密耦合的服务.将来,我需要减少服务之间的依赖关系数量,或者通过使用事件来放松它们.
Another issue was that the AuthHttp
service was not provided in AccountsWebapi
. This was easily solved by declaring the HttpModule
as a dependency in the testBed imports. Also, this was a good opportunity for me to learn that I have several tightly coupled services that need to be provided together in the TestBed. In the future, I need to reduce the number of dependencies between services or loosen them by using events.
完成所有这些修改后,可以轻松实例化TestBed,而无需将EffectClass包装在负责初始化AccountWebapi
的另一层中.由于对依赖项注入框架缺乏正确的了解,因此该解决方案的设计过度了.
After finishing all these modifications the TestBed can be easily instantiated without the need to wrap the EffectsClass in another layer that takes care of initing the AccountWebapi
. That was a grossly overengineered solution due to lack of proper understanding of the dependency injection framework.
这篇关于Ngrx测试-如何配置TestBed以便在测试中实例化整个状态存储的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!