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
        });
    }
}

而且我可以在应用程序中看到它有效。我已经手动测试过了。但是,我目前正在尝试编写单元测试,而且我一直在超时。我已经 运行 解决了这个问题几次,但我总是能够在我的断言中使用 fakeAsyncdone 回调来解决它。

测试写法如下:

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.

我已经尝试了 BehaviourSubjects、fakeAsyncdone() 回调,我已经移动了 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();
                });
            });
        });
    });
});

您的示例中几乎没有主要危险信号:

  1. 触发订阅者的代码在 beforeEach 内,而不是在实际的 it 单元测试范围内。调用您正在测试的功能的代码在单元测试的主体中很重要,因为您希望在它执行后直接测试其结果,而不是将断言分离到测试套件调用的不同函数中而不是你。这在使用异步代码时尤其重要,因为您需要确保事情在正确的时间执行,并且 beforeEachit 可能有时间差异。为了完整起见,beforeEach 用于设置正在测试的 component/service 的状态,以尽量减少重复测试设置逻辑(given/arrange)并且绝对不打算使用执行测试逻辑。

  2. 当测试 pub sub 代码时,你想先订阅 observable,然后才发布 (next) 给它,所以你需要切换这两个执行产生你想要的结果。