Jest/Rxjs - 从 getter 调用订阅时超时
Jest/Rxjs - timeout when calling for subscription from a getter
我有一个服务,每当调用服务中的方法时,它都会更新 Rxjs Subject
:
@Injectable()
export class AppAlertService implements IAppAlertService {
private readonly _alertBehaviourSubject: Subject<IAlertConfiguration> = new Subject<IAlertConfiguration>();
public get alertConfiguration(): Observable<IAlertConfiguration> {
return this._alertBehaviourSubject.asObservable();
}
public alertError(alertMessage: string, alertOptions?: IAlertOptions, alertCtaClickCallback?: AlertOptionCallback): void {
this._alertBehaviourSubject.next({
message: alertMessage,
options: alertOptions ? alertOptions : { displayCloseLogo: true },
type: 'error',
callback: alertCtaClickCallback
});
}
}
而且我可以在应用程序中看到它有效。我已经手动测试过了。但是,我目前正在尝试编写单元测试,而且我一直在超时。我已经 运行 解决了这个问题几次,但我总是能够在我的断言中使用 fakeAsync
或 done
回调来解决它。
测试写法如下:
describe('AppAlertService', () => {
let subject: AppAlertService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [AppAlertService]
})
.compileComponents()
.then(() => {
subject = TestBed.inject<AppAlertService>(AppAlertService);
});
});
describe('Given an alert error', () => {
beforeEach(() => {
subject.alertError('Some Mock Alert Message', { displayCloseLogo: true });
});
it('Then the config is mapped correctly', (done) => {
subject.alertConfiguration
.pipe(first())
.subscribe({
next: (config: IAlertConfiguration) => {
expect(config).toEqual(false);
done();
},
});
});
});
});
如果我将 Subject<T>
更改为 BehaviourSubject<T>
,我可以通过此测试,但我不希望它在构建时触发,所以我选择了一个主题。此外,断言是完全错误的 -> configuration
永远不会是 boolean
.
我已经尝试了 BehaviourSubject
s、fakeAsync
、done()
回调,我已经移动了 done 回调,我已经解决了对 subject.alertConfiguration
的调用到 Promise<T>
但它仍然失败,我已将超时增加到 30 秒......我很难过。
编辑!
感谢 Ovidijus Parsiunas 在下面的回答。我意识到 beforeEach 钩子和测试之间存在竞争条件。我已经设法让测试工作:
import { AppAlertService } from './app-alert.service';
import { TestBed } from '@angular/core/testing';
import { IAlertConfiguration } from '../types/alert-configuration.interface';
describe('AppAlertService', () => {
let subject: AppAlertService;
beforeEach(async () => {
await TestBed.configureTestingModule({
providers: [AppAlertService]
});
subject = TestBed.inject<AppAlertService>(AppAlertService);
});
describe('Given an alert', () => {
describe('When a minimal config is provided', () => {
it('Then the config is mapped correctly', (done) => {
subject.alertConfiguration
.subscribe((result: IAlertConfiguration) => {
expect(result).toEqual({
callback: undefined,
message: 'Some Mock Alert Message',
options: {
displayCloseLogo: true
},
type: 'error'
});
done();
});
subject.alertError('Some Mock Alert Message', { displayCloseLogo: true });
});
});
describe('Given an alert with a callback attached to the parameters', () => {
describe('When invoking the callback', () => {
const mockCallBack = jest.fn();
beforeEach(() => {
jest.spyOn(mockCallBack, 'mockImplementation');
});
it('Then the callback can be called', (done) => {
subject.alertConfiguration
.subscribe((result: IAlertConfiguration) => {
const resultCallback = result.callback as () => any;
resultCallback().mockImplementation();
done();
});
subject.alertError('Some Mock Alert Message', { displayCloseLogo: true }, () => mockCallBack);
});
it('Then the function is called once', () => {
expect(mockCallBack.mockImplementation).toHaveBeenCalled();
});
});
});
});
});
您的示例中几乎没有主要危险信号:
触发订阅者的代码在 beforeEach
内,而不是在实际的 it
单元测试范围内。调用您正在测试的功能的代码在单元测试的主体中很重要,因为您希望在它执行后直接测试其结果,而不是将断言分离到测试套件调用的不同函数中而不是你。这在使用异步代码时尤其重要,因为您需要确保事情在正确的时间执行,并且 beforeEach
和 it
可能有时间差异。为了完整起见,beforeEach
用于设置正在测试的 component/service 的状态,以尽量减少重复测试设置逻辑(given/arrange)并且绝对不打算使用执行测试逻辑。
当测试 pub sub 代码时,你想先订阅 observable,然后才发布 (next
) 给它,所以你需要切换这两个执行产生你想要的结果。
我有一个服务,每当调用服务中的方法时,它都会更新 Rxjs Subject
:
@Injectable()
export class AppAlertService implements IAppAlertService {
private readonly _alertBehaviourSubject: Subject<IAlertConfiguration> = new Subject<IAlertConfiguration>();
public get alertConfiguration(): Observable<IAlertConfiguration> {
return this._alertBehaviourSubject.asObservable();
}
public alertError(alertMessage: string, alertOptions?: IAlertOptions, alertCtaClickCallback?: AlertOptionCallback): void {
this._alertBehaviourSubject.next({
message: alertMessage,
options: alertOptions ? alertOptions : { displayCloseLogo: true },
type: 'error',
callback: alertCtaClickCallback
});
}
}
而且我可以在应用程序中看到它有效。我已经手动测试过了。但是,我目前正在尝试编写单元测试,而且我一直在超时。我已经 运行 解决了这个问题几次,但我总是能够在我的断言中使用 fakeAsync
或 done
回调来解决它。
测试写法如下:
describe('AppAlertService', () => {
let subject: AppAlertService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [AppAlertService]
})
.compileComponents()
.then(() => {
subject = TestBed.inject<AppAlertService>(AppAlertService);
});
});
describe('Given an alert error', () => {
beforeEach(() => {
subject.alertError('Some Mock Alert Message', { displayCloseLogo: true });
});
it('Then the config is mapped correctly', (done) => {
subject.alertConfiguration
.pipe(first())
.subscribe({
next: (config: IAlertConfiguration) => {
expect(config).toEqual(false);
done();
},
});
});
});
});
如果我将 Subject<T>
更改为 BehaviourSubject<T>
,我可以通过此测试,但我不希望它在构建时触发,所以我选择了一个主题。此外,断言是完全错误的 -> configuration
永远不会是 boolean
.
我已经尝试了 BehaviourSubject
s、fakeAsync
、done()
回调,我已经移动了 done 回调,我已经解决了对 subject.alertConfiguration
的调用到 Promise<T>
但它仍然失败,我已将超时增加到 30 秒......我很难过。
编辑!
感谢 Ovidijus Parsiunas 在下面的回答。我意识到 beforeEach 钩子和测试之间存在竞争条件。我已经设法让测试工作:
import { AppAlertService } from './app-alert.service';
import { TestBed } from '@angular/core/testing';
import { IAlertConfiguration } from '../types/alert-configuration.interface';
describe('AppAlertService', () => {
let subject: AppAlertService;
beforeEach(async () => {
await TestBed.configureTestingModule({
providers: [AppAlertService]
});
subject = TestBed.inject<AppAlertService>(AppAlertService);
});
describe('Given an alert', () => {
describe('When a minimal config is provided', () => {
it('Then the config is mapped correctly', (done) => {
subject.alertConfiguration
.subscribe((result: IAlertConfiguration) => {
expect(result).toEqual({
callback: undefined,
message: 'Some Mock Alert Message',
options: {
displayCloseLogo: true
},
type: 'error'
});
done();
});
subject.alertError('Some Mock Alert Message', { displayCloseLogo: true });
});
});
describe('Given an alert with a callback attached to the parameters', () => {
describe('When invoking the callback', () => {
const mockCallBack = jest.fn();
beforeEach(() => {
jest.spyOn(mockCallBack, 'mockImplementation');
});
it('Then the callback can be called', (done) => {
subject.alertConfiguration
.subscribe((result: IAlertConfiguration) => {
const resultCallback = result.callback as () => any;
resultCallback().mockImplementation();
done();
});
subject.alertError('Some Mock Alert Message', { displayCloseLogo: true }, () => mockCallBack);
});
it('Then the function is called once', () => {
expect(mockCallBack.mockImplementation).toHaveBeenCalled();
});
});
});
});
});
您的示例中几乎没有主要危险信号:
触发订阅者的代码在
beforeEach
内,而不是在实际的it
单元测试范围内。调用您正在测试的功能的代码在单元测试的主体中很重要,因为您希望在它执行后直接测试其结果,而不是将断言分离到测试套件调用的不同函数中而不是你。这在使用异步代码时尤其重要,因为您需要确保事情在正确的时间执行,并且beforeEach
和it
可能有时间差异。为了完整起见,beforeEach
用于设置正在测试的 component/service 的状态,以尽量减少重复测试设置逻辑(given/arrange)并且绝对不打算使用执行测试逻辑。当测试 pub sub 代码时,你想先订阅 observable,然后才发布 (
next
) 给它,所以你需要切换这两个执行产生你想要的结果。