嘲笑/rxjs-从getter求订阅时超时

发布于 2025-01-31 04:33:18 字数 4843 浏览 2 评论 0原文

我有一项服务,可以在调用服务中的方法时更新RXJS 主题

@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完成在我的主张中回调。

测试如下:

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

        });
    });
});

如果我将主题&lt; t&gt;更改为capen> candy> caveniorSubject&lt; t&gt;,我可以通过此测试进行通过。在施工中,我选择了一个主题。而且,断言是完全错误的 - &gt; 配置永远不会是boolean

我已经尝试了caveniourSubject s,fakeasyncdone()回调,我已经将完成的回调移动了,我已经解决了调用到objection.alertconfigurationpromise&lt; t&gt;仍然失败,我将超时增加到30秒...我很困惑。

编辑!

感谢Ovidijus Parsiunas的答案。我意识到,在前钩和测试之间存在种族条件。我设法使测试工作:

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

I have a service that updates an Rxjs Subject whenever a method in the service is called:

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

And I can see that in the application, it works. I've manually tested it. However, I'm currently trying to write a unit test, and I keep timing out. I've ran into this issue a few time but I've always been able to resolve it using fakeAsync or a done callback in my assertion.

The test is written as follows:

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

        });
    });
});

I can get this test to pass if I change Subject<T> over to BehaviourSubject<T> but I don't want it to fire on construction so I chose a subject. Also, the assertion is completely wrong -> configuration will never be a boolean.

I've tried BehaviourSubjects, fakeAsync, done() callbacks, I've moved the done callback around, I've resolved the call to subject.alertConfiguration to a Promise<T> and it still fails, I've increased the timeout to 30 seconds... I'm stumped.

EDIT!

Thanks to Ovidijus Parsiunas' answer below. I realised there's a race condition between the beforeEach hook and the test. I've managed to get the test working:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

倾`听者〃 2025-02-07 04:33:18

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

  1. 触发订户的代码在之前内,而不是在实际 IT 单元测试范围内。对于指示您要测试的功能在单位测试正文中,您要在执行后直接测试其结果,而不是将主张转换为由测试套件调用的其他功能,这一点很重要。而不是你。当使用异步代码时,这一点尤其重要,因为您需要确保在正确的时间执行内容,并且 and it 可能会有时间差异。为了完整性,用于设置正在测试的组件/服务的状态,以最大程度地减少重复的测试设置逻辑(给定/安排),并且绝对不打算用于执行测试逻辑。

  2. 测试Pub子代码时,您要首先订阅可观察的可观察到的,然后以后发布(next),因此您需要切换这两个执行,以产生您的结果欲望。

There are few major red flags within your example:

  1. The code that is triggering your subscriber is inside a beforeEach instead of being inside the actual it unit test scope. It is important for code that invokes the functionality which you are testing to be in the body of a unit test as you want to test its results directly after it has executed and not decouple the assertion into a different function that is invoked by the test suite and not you. This is especially important when working with asynchronous code as you need to make sure things are executed at correct times and beforeEach and it could have a time differential. For completeness, beforeEach is used for setting up the state of the component/service that is being tested, to minimise the repeated test setup logic (given/arrange) and is most definitely not intended to be used to execute logic for the test.

  2. When testing pub sub code, you want to first subscribe the observable, and only later publish (next) to it, so you will need to switch these two executions around which should produce the results you desire.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文