我必须测试一个使用 fromEvent 可观察函数的函数。 在升级为“可出租”运营商之前,我只是这样做:
spyOn(Observable, 'fromEvent').and.callFake(mockFromEventFunction)
但是现在,Rxjs 已经改变了,Observable.fromEvent 只是一个名为 fromEvent 的函数,它是这样导入的:(并以相同的方式使用)
import { fromEvent } from 'rxjs/observable/fromEvent';
我的问题是,如何在不知道它的父上下文的情况下使用 Jasmine 间谍实用程序模拟该函数?
我建议这不起作用:
import * as FromEventContext from 'rxjs/observable/fromEvent';
...
spyOn(FromEventContext , 'fromEvent').and.callFake(mockFromEventFunction)
现在我有一种解决方法,将 fromEvent 包装在一个我知道上下文的对象中。但我想知道如何才能干净地解决这个问题。
提前致谢。
经过一番调查,我发现我们能否模拟这个单个导出函数直接取决于我们的捆绑器在测试时如何解析模块。
例如,您可能会偶然发现此错误或类似错误:
Error: : myFunctionName is not declared writable or has no setter
因为捆绑器只是将那些孤独的导出函数包装到 getter 属性中,使得它们无法被模拟。
我最终使用的解决方案是测试时在
'commonjs'
中编译模块。
例如,如果您正在使用 typescript,则需要更改
tsconfig.spec.ts
以使用 commonjs 模块:
"compilerOptions": {
....
// force commonjs module output, since it let mock exported members on modules to anywhere in the application (even in the same file)
"module": "commonjs",
},
commonjs 中模块的任何导出成员的最终输出将类似于:
exports.myFunc = function() {}
。这使得使用spyOn无需担心,因为它包装在“导出”对象上。一个很好的用例是,它可以在任何地方被嘲笑,包括其自己文件中的用法!
示例:
// some-module.js
export function functionToMock() {
return 'myFuncToMock';
}
export function functionToTest() {
return functionToMock();
}
// testing-module.spec.js
import * as SomeModule from ./some-module
spyOn(SomeModule, 'functionToMock').and.returnValue('mockedCorrectly');
SomeModule.functionToTest().toBe('mockedCorrectly')
我还偶然发现了在 jasmine 中测试我的组件的问题,该组件具有
fromEvent
rxjs 运算符。我的组件看起来像这样:
@ViewChild('searchBar') searchBarRef: ElementRef;
@Input() courses$: any[];
filteredCourses$: any[];
ngAfterViewInit(): void {
this.filteredCourses$ = fromEvent(this.searchBarRef.nativeElement, 'keyup').pipe(
map((event: any) => event.srcElement.value),
startWith(''),
distinctUntilChanged(),
switchMap((searchText) =>
this.courses$.pipe(
map((courses) =>
courses.filter(
(course) =>
course.code.toLowerCase().includes(searchText.toLowerCase()) ||
course.name.toLowerCase().includes(searchText.toLowerCase())
)
)
)
),
shareReplay(1)
);
}
为了测试
ngAfterViewInit
,我创建了一个模拟输入元素并将其分配给nativeElement
:
it('ngAfterViewInit():', () => {
const mockInputElement = document.createElement('input');
component.searchBarRef = {
nativeElement: mockInputElement
};
const mockCourses = of([
{
code: 'PHY',
name: 'Physics',
},
{
code: 'MAT',
name: 'Mathematics',
},
{
code: 'BIO',
name: 'Biology',
}
]);
component.courses$ = mockCourses;
component.ngAfterViewInit();
component.filteredCourses$.pipe(take(1)).subscribe((res) => {
expect(res).toEqual(mockCourses);
});
mockInputElement.value = 'mat' // pass the input to test
mockInputElement.dispatchEvent(new Event('keyup'));
component.filteredCourses$.pipe(take(1)).subscribe((res) => {
expect(res).toEqual([{
code: 'MAT',
name: 'Mathematics',
}]);
});
});
您可以使用此逻辑来测试您的组件。
你是对的。 FromEventContext 对此不起作用。
您可以使用 jasmine-auto-spies,这样可以更轻松地处理此类事情。
import { Spy, createSpyFromClass } from 'jasmine-auto-spies';
在您的测试代码中,您现在可以在 Observables 上创建间谍:
fakeObservable = createSpyFromClass(Observable);
fakeObservable.fromEvent.and.returnValue(Observable.empty()) // or whatever you want to return
也许这有帮助?!
不知道这是否是你的情况,但如果你
spyOnProperty
在fromEvent
上,它可能会起作用。
像这样:
spyOnProperty(rxjs, 'fromEvent').and.returnValue(() => rxjs.of({}));
就我而言,我导入整个
rxjs
库
import * as rxjs from 'rxjs';
希望有帮助, 干杯!
我找到了一个解决方法,使用 Object.defineProperty 使
fromEvent
可写:
import * as rxjs from "rxjs";
// makes it writable
Object.defineProperty(rxjs, 'fromEvent', {
value: rxjs.fromEvent,
writable: true
});
// now you can spy
const mockEvent$ = new Subject<Event>();
spyOn(rxjs, "fromEvent").and.returnValue(mockEvent$);