单元测试 NgRx 效果以确保服务方法被调用 - 不工作
Unit testing NgRx effect to ensure the service method was called - ain't working
我使用的是 NgRx ^7.0.0 版本。
这是我的 NgRx 效果 class:
import { Injectable } from '@angular/core';
import { ApisService } from '../apis.service';
import { Effect, Actions, ofType } from '@ngrx/effects';
import { Observable } from 'rxjs';
import { ApisActionTypes, ApisFetched } from './apis.actions';
import { mergeMap, map } from 'rxjs/operators';
@Injectable()
export class ApisEffects {
constructor(private apisS: ApisService, private actions$: Actions) { }
@Effect()
$fetchApisPaths: Observable<any> = this.actions$.pipe(
ofType(ApisActionTypes.FetchApisPaths),
mergeMap(() =>
this.apisS.fetchHardCodedAPIPaths().pipe(
map(res => new ApisFetched(res))
)
)
);
}
这是一个简单的测试。如您所见,它应该失败,但总是通过。
我在 Whosebug 上关注了类似的问题,但它对我不起作用,就好像代码执行从未进入效果一样。$fetchApisPaths.subscribe block
import { TestBed } from '@angular/core/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { hot, cold } from 'jasmine-marbles';
import { Observable, ReplaySubject } from 'rxjs';
import { ApisEffects } from '../state/apis.effects';
import { ApisFetch, ApisFetched } from '../state/apis.actions';
import { IApiPath } from '../models';
import { convertPaths, getAPIPathsAsJson, ApisService } from '../apis.service';
import { ApisServiceMock } from './mocks';
describe('Apis Effects', () => {
let effects: ApisEffects;
let actions: Observable<any>;
let apisS: ApisService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
ApisEffects,
provideMockActions(() => actions),
{
provide: ApisService,
useClass: ApisServiceMock
}
]
});
effects = TestBed.get(ApisEffects);
apisS = TestBed.get(ApisService);
});
it('should call ApisService method() to get Api Paths', () => {
const spy = spyOn(apisS, 'fetchHardCodedAPIPaths');
const action = new ApisFetch();
actions = hot('--a-', {a: action});
effects.$fetchApisPaths.subscribe(() => {
console.log('%c effect trigerred', 'color: orange; border: 1px solid red;');
// expect(spy).toHaveBeenCalled();
expect(true).toBe(false); // never fails
});
});
});
以防万一我用动作做 smthg 愚蠢的事情,这里是动作文件:
我很可能不是,因为它在应用程序中按预期工作。
import { Action } from '@ngrx/store';
import { IApiPath } from '../models';
export enum ApisActionTypes {
FetchApisPaths = '[Apis] Fetch Paths',
FetchedApisPaths = '[Apis] Fetched Paths'
}
export class ApisFetch implements Action {
readonly type = ApisActionTypes.FetchApisPaths;
}
export class ApisFetched implements Action {
readonly type = ApisActionTypes.FetchedApisPaths;
constructor(public payload: IApiPath[]) {}
}
export type ApisActions = ApisFetch | ApisFetched;
=======================编辑===================== =========
我使用了官方 ngrx 文档中的示例 https://ngrx.io/guide/effects/testing 现在我可以成功进入下面的订阅块,我记录了两个控制台日志,但测试成功。这很奇怪!我已经尝试从订阅块中抛出错误,但测试仍然成功。
it('should work also', () => {
actions$ = new ReplaySubject(1);
actions$.next(new ApisFetch());
effects.$fetchApisPaths.subscribe(result => {
console.log('will be logged');
expect(true).toBe(false); // should fail but nothing happens - test succeeds
console.log(' --------- after '); // doesn't get called, so the code
// execution stops on expect above
});
});
好的,我让它工作了。为了成功测试是否从 NgRx effect 中调用了特定的 Angular 服务方法,我将测试用例包装在 async
:
中
it('should call ApisService method to fetch Api paths', async () => {
const spy = spyOn(apisS, 'fetchHardCodedAPIPaths');
actions$ = new ReplaySubject(1);
actions$.next(new ApisFetch());
await effects.$fetchApisPaths.subscribe();
expect(spy).toHaveBeenCalled();
});
我 await effects.$fetchApisPaths.subscribe();
阻止执行并 运行 在下一行测试断言。
现在当我尝试 运行 expect(true).toBe(false);
来测试测试是否失败时,它正确地失败了。
问题中我的代码的问题(ngrx 文档 https://ngrx.io/guide/effects/testing 中带有 ReplaySubject
的示例)是当断言在 [=17 内时不可能使测试失败=] 块。那里发生了一些可疑的事情,我仍然不知道为什么代码会以下列方式运行:
effects.$fetchApisPaths.subscribe(result => {
console.log('will be logged'); // 1) gets logged
expect(true).toBe(false); // 2) should fail
console.log(' - after '); // 3) doesn't get called
});
所以代码执行停止在行 2),测试用例 returns 为正,行 3) 永远不会执行.
因此,在 .subscribe()
块内带有断言的 ngrx 文档中的测试用例将始终为绿色,从而为您的测试用例提供误报。这是我在 ngrx ^7.0.0
中遇到的行为
编辑 2020 年 9 月 - 针对 ngrx 版本 9 进行了更新。
如果上面的解决方案对您或将来对我不起作用,因为我再次遇到同样的问题并且只能找到我自己的帮助答案和@Christian 的精彩评论将我引向 ngrx gitter 问题,试试这个方法:
it('should call ApisService method to fetch Api paths', async () => {
const spy = spyOn(apisS, 'fetchHardCodedAPIPaths');
actions$ = cold('--a-', {
a: ControlCenterTrendsLineChartPeriodChange({ numberOfMonths: 24 })
});
await effects.$fetchApisPaths.subscribe();
expect(actions$).toSatisfyOnFlush(() => {
expect(spy).toHaveBeenCalled();
});
我使用的是 NgRx ^7.0.0 版本。 这是我的 NgRx 效果 class:
import { Injectable } from '@angular/core';
import { ApisService } from '../apis.service';
import { Effect, Actions, ofType } from '@ngrx/effects';
import { Observable } from 'rxjs';
import { ApisActionTypes, ApisFetched } from './apis.actions';
import { mergeMap, map } from 'rxjs/operators';
@Injectable()
export class ApisEffects {
constructor(private apisS: ApisService, private actions$: Actions) { }
@Effect()
$fetchApisPaths: Observable<any> = this.actions$.pipe(
ofType(ApisActionTypes.FetchApisPaths),
mergeMap(() =>
this.apisS.fetchHardCodedAPIPaths().pipe(
map(res => new ApisFetched(res))
)
)
);
}
这是一个简单的测试。如您所见,它应该失败,但总是通过。
我在 Whosebug
import { TestBed } from '@angular/core/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { hot, cold } from 'jasmine-marbles';
import { Observable, ReplaySubject } from 'rxjs';
import { ApisEffects } from '../state/apis.effects';
import { ApisFetch, ApisFetched } from '../state/apis.actions';
import { IApiPath } from '../models';
import { convertPaths, getAPIPathsAsJson, ApisService } from '../apis.service';
import { ApisServiceMock } from './mocks';
describe('Apis Effects', () => {
let effects: ApisEffects;
let actions: Observable<any>;
let apisS: ApisService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
ApisEffects,
provideMockActions(() => actions),
{
provide: ApisService,
useClass: ApisServiceMock
}
]
});
effects = TestBed.get(ApisEffects);
apisS = TestBed.get(ApisService);
});
it('should call ApisService method() to get Api Paths', () => {
const spy = spyOn(apisS, 'fetchHardCodedAPIPaths');
const action = new ApisFetch();
actions = hot('--a-', {a: action});
effects.$fetchApisPaths.subscribe(() => {
console.log('%c effect trigerred', 'color: orange; border: 1px solid red;');
// expect(spy).toHaveBeenCalled();
expect(true).toBe(false); // never fails
});
});
});
以防万一我用动作做 smthg 愚蠢的事情,这里是动作文件: 我很可能不是,因为它在应用程序中按预期工作。
import { Action } from '@ngrx/store';
import { IApiPath } from '../models';
export enum ApisActionTypes {
FetchApisPaths = '[Apis] Fetch Paths',
FetchedApisPaths = '[Apis] Fetched Paths'
}
export class ApisFetch implements Action {
readonly type = ApisActionTypes.FetchApisPaths;
}
export class ApisFetched implements Action {
readonly type = ApisActionTypes.FetchedApisPaths;
constructor(public payload: IApiPath[]) {}
}
export type ApisActions = ApisFetch | ApisFetched;
=======================编辑===================== =========
我使用了官方 ngrx 文档中的示例 https://ngrx.io/guide/effects/testing 现在我可以成功进入下面的订阅块,我记录了两个控制台日志,但测试成功。这很奇怪!我已经尝试从订阅块中抛出错误,但测试仍然成功。
it('should work also', () => {
actions$ = new ReplaySubject(1);
actions$.next(new ApisFetch());
effects.$fetchApisPaths.subscribe(result => {
console.log('will be logged');
expect(true).toBe(false); // should fail but nothing happens - test succeeds
console.log(' --------- after '); // doesn't get called, so the code
// execution stops on expect above
});
});
好的,我让它工作了。为了成功测试是否从 NgRx effect 中调用了特定的 Angular 服务方法,我将测试用例包装在 async
:
it('should call ApisService method to fetch Api paths', async () => {
const spy = spyOn(apisS, 'fetchHardCodedAPIPaths');
actions$ = new ReplaySubject(1);
actions$.next(new ApisFetch());
await effects.$fetchApisPaths.subscribe();
expect(spy).toHaveBeenCalled();
});
我 await effects.$fetchApisPaths.subscribe();
阻止执行并 运行 在下一行测试断言。
现在当我尝试 运行 expect(true).toBe(false);
来测试测试是否失败时,它正确地失败了。
问题中我的代码的问题(ngrx 文档 https://ngrx.io/guide/effects/testing 中带有 ReplaySubject
的示例)是当断言在 [=17 内时不可能使测试失败=] 块。那里发生了一些可疑的事情,我仍然不知道为什么代码会以下列方式运行:
effects.$fetchApisPaths.subscribe(result => {
console.log('will be logged'); // 1) gets logged
expect(true).toBe(false); // 2) should fail
console.log(' - after '); // 3) doesn't get called
});
所以代码执行停止在行 2),测试用例 returns 为正,行 3) 永远不会执行.
因此,在 .subscribe()
块内带有断言的 ngrx 文档中的测试用例将始终为绿色,从而为您的测试用例提供误报。这是我在 ngrx ^7.0.0
编辑 2020 年 9 月 - 针对 ngrx 版本 9 进行了更新。 如果上面的解决方案对您或将来对我不起作用,因为我再次遇到同样的问题并且只能找到我自己的帮助答案和@Christian 的精彩评论将我引向 ngrx gitter 问题,试试这个方法:
it('should call ApisService method to fetch Api paths', async () => {
const spy = spyOn(apisS, 'fetchHardCodedAPIPaths');
actions$ = cold('--a-', {
a: ControlCenterTrendsLineChartPeriodChange({ numberOfMonths: 24 })
});
await effects.$fetchApisPaths.subscribe();
expect(actions$).toSatisfyOnFlush(() => {
expect(spy).toHaveBeenCalled();
});