1

I've been trying to unit test the subscribe function of this service. And looking at the code coverage report generated by istanbul, I can see that this code is not covered.

Code

layout.component.ts

import {Component, HostListener, Input} from '@angular/core';

import { LayoutService } from './layout.service';

import { some } from 'lodash';

@Component({
    selector: 'cgm-layout',
    templateUrl: './layout.component.html',
    styleUrls: ['./layout.component.scss'],
    providers: [LayoutService]
})
class LayoutComponent {

    message: any;

    constructor(
        private service: LayoutService
    ) {
        service.messagePublished$.subscribe(
            message => {
                this.setMessage(message);
            }
        );
    }

    setMessage(message): void {
        this.message = message;
        setTimeout(() => {
            this.message = null;
        }, 7000);
    }

}

export {
    LayoutComponent
};

This is my Unit Test

layout.component.spec.ts

import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { of } from 'rxjs';
import { LayoutComponent } from './layout.component';
import { LayoutService } from './layout.service';

describe('LayoutComponent', () => {
    let component: LayoutComponent;
    let fixture: ComponentFixture<LayoutComponent>;
    let service;

    beforeEach(async(() => {
        service = new LayoutService();
        mockLayoutService = jasmine.createSpyObj('LayoutService', ['messagePublished$']);
        TestBed.configureTestingModule({
            declarations: [
                LayoutComponent,

            ],
            providers: [
                LayoutService
            ],
            schemas: [
                NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA
            ]
        })
            .compileComponents();
    }));

    beforeEach(() => {
        fixture = TestBed.createComponent(LayoutComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();

        component.message = 'Garbage';
    });

    it('should call messagePublished', () => {
        spyOn(service.messagePublished$, 'subscribe');
        TestBed.createComponent(LayoutComponent);

        expect(service.messagePublished$.subscribe).toHaveBeenCalled();
    });

    describe('setMessage', () => {

        it('should set the Message', fakeAsync(() => {
            component.setMessage('Message');

            expect(component.message).toBe('Message');
            tick(7000);
            expect(component.message).toBeNull();
        }));

    });
});

So the code never seems to go over the 'service.messagePublished$.subscribe' part. Here is the code coverage report.

The error I'm getting is 'Expected spy subscribe to have been called', which I'm guessing is the error you get when that code block is not covered.

1 Answer 1

2

I'd advise you to move your subscription from the constructor to an ngOnInit. Angular created several lifecycle hooks which get called when a component get's created (ngOnInit) and other when data changes or when it gets destroyed - see Angular lifecycle hooks.

This way you can test your code by calling the ngOnInit() method.

In case you cannot change the code you can try creating a component instance and check if your method was called like in pseudo-code below:

import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { of } from 'rxjs';
import { LayoutComponent } from './layout.component';
import { LayoutService } from './layout.service';

describe('LayoutComponent', () => {
    let component: LayoutComponent;
    let fixture: ComponentFixture<LayoutComponent>;
    let serviceSpy: jasmine.SpyObj<LayoutService>;;

    beforeEach(async(() => {
        const spy = spyOn(service.messagePublished$, 'subscribe')
        TestBed.configureTestingModule({
            declarations: [
                LayoutComponent,

            ],
            providers: [
                { provide: LayoutService, useValue: spy }
            ],
            schemas: [
                NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA
            ]
        })
            .compileComponents();
            serviceSpy = TestBed.get(ValueService);
    }));

    beforeEach(() => {
        fixture = TestBed.createComponent(LayoutComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();

        component.message = 'Garbage';
    });

    it('should call messagePublished', () => {
        TestBed.createComponent(LayoutComponent);

        expect(service.messagePublished$.subscribe).toHaveBeenCalled();
    });

});
Sign up to request clarification or add additional context in comments.

9 Comments

That's a good idea but I'm only in charge of unit testing right now. So I can't really change the code. I have to figure out a way to access the code when it's in the constructor.
Updated the code for testing constructor tests. Hopefully it should give you an idea to how to check if the code has been run.
It is because you replace your LayoutService with a mock in this line: provide: LayoutService, useValue: mockLayoutService and mockLayoutService doesn't have a property of messagePublished$. mockLayoutService needs to be an object with a property messagePublished$ which has a subscribe property.
Code updated. I hope it gives you a clearer picture.
To insantiate your service replace service = new LayoutService(); with service = TestBed.get(LayoutService); after the configureTestingModule() method
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.