Jest/Rxjs – timeout when calling for subscription from a getter -- [Question Asked]

Query asked by user

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

Answer we found from sources

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.

Answered By – Ovidijus Parsiunas

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0


What is Angular?

Angular is an open-source, JavaScript outline written in TypeScript. Google keeps up with it, and its basic role is to foster single-page activities. As an edge, Angular enjoys clear benefits while likewise outfitting a standard design for formulators to work with. It empowers stoners to deliver huge tasks in a viable way. textures overall lift web improvement viability and execution by outfitting an agreeable construction so that formulators do n't need to continue to modify regulation from scratch. textures are efficient devices that offer formulators a large group of extra elements that can be added to programming easily.

However, is JavaScript ideal for creating single-sprinter activities that bear particularity, testability, and trend-setter efficiency? maybe not.

JavaScript is the most by and large utilized client-side prearranging language. It's composed into HTML reports to empower relations with web sprinters in endless extraordinary ways. As a genuinely simple to-learn language with inescapable help, creating current operations is appropriate.

Nowadays, we have various textures and libraries intended to give essential outcomes. As for front end web advancement, Angular addresses incalculable, while possibly not all, of the issues formulators face while utilizing JavaScript all alone.
Who we are?

We are team of software engineers in multiple domains like Programming and coding, Fundamentals of computer science, Design and architecture, Algorithms and data structures, Information analysis, Debugging software and Testing software. We are working on Systems developer and application developer. We are curious, methodical, rational, analytical, and logical. Some of us are also conventional, meaning we're conscientious and conservative.

Answer collected from stackoverflow and other sources, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0