Search code examples
angulartestingjasmineeventemitter

Angular2 testing a module with an EventEmitter subscription in ngOnInit error


I am having trouble writing a testing module for an Angular2 component I have created. The component in question subscribes to another service's EventEmitter in it's ngOnInit method. The component is then subscribed to this EventEmitter and listens for changes. Here is the component in question:

import { Component } from "@angular/core";
import { Product } from "../../../classes/Product";
import { ProductService } from "../../../services/product.service";
import { ConfigObject } from "../../../ConfigObject";
import { productHelper } from "../../../helpers/productHelper";


@Component({
    selector: 'product-component',
    templateUrl: '/app/views/catalog/products/product-dashboard.html',
    moduleId: module.id
})

export class ProductComponent {
    globals = ConfigObject;
    products: Product[] = [];
    productsLoaded = false;

    productPayload = {
        order           : 'asc',
        order_by        : 'title',
        category_id     : 0,
        resize          : true,
        imgHeight       : 200,
        imgWidth        : 200,
        active          : 1,
        searchTerm      : '',
        manufacturer    : null
    };

    constructor(
        private _productService: ProductService
    ) {

    }

    getProducts(filters) {
        this.productsLoaded = false;

        this._productService.getProducts(filters)
            .subscribe(
                products => { this.products = productHelper.processImagesAndDownloads(products)},
                ()       => { },
                ()       => { this.productsLoaded = true }
        );
    }

    ngOnInit() {
        this._productService.emitter.subscribe(
            (products) => {
                this.products = productHelper.processImagesAndDownloads(products);
            },
            ()       => { },
            ()       => { }
        );

        this.getProducts({});
    }

}

As you can see, using the ngOnInit method the _productService.emitter EventEmitter is subscribed to.

I have attempted to use the spyOn method to mock this event emitter but with no success. I cannot seem to get this component testing correctly. Can anyone see what the problem is here:

import { ProductService } from "../../../../services/product.service";
import { TestBed, ComponentFixture, async } from "@angular/core/testing";
import { ProductComponent } from "../../../../components/catalog/products/ProductComponent";
import { HttpModule } from "@angular/http";
import { DebugElement, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";

let MockProductService = {
    emitter: () => {}
};

let comp:    ProductComponent;
let fixture: ComponentFixture<ProductComponent>;
let de:      DebugElement;
let el:      HTMLElement;

describe('Component: Product Component', () => {
    beforeEach(() => {
        TestBed.configureTestingModule({
            declarations: [
                ProductComponent
            ],
            providers: [
                {
                    provide: ProductService, useValue: MockProductService
                }
            ],
            imports: [
                HttpModule
            ],
            schemas: [CUSTOM_ELEMENTS_SCHEMA]
        });
    });

    it('Should check that products are loaded in the template', async(() => {

        TestBed.compileComponents()
            .then(() => {

                fixture = TestBed.createComponent(ProductComponent);
                comp = fixture.componentInstance;

                spyOn(MockProductService, 'emitter').and.returnValue({
                    subscribe: () => {}
                });
                comp.ngOnInit();

                expect(MockProductService.emitter).toHaveBeenCalled();
        });
    }));

});

The error I receive is:

Failed: Uncaught (in promise): TypeError: this._productService.emitter.subscribe is not a function

Solution

  • emitter doesn't get called as a method from the component. It is only accessed as a property

    this._productService.emitter
    

    And because it never gets called as a method, your spy is useless.

    You could just assign the value of the emitter to an Observable. That way, when the component subscribes, it actually gets a value

    import 'rxjs/add/observable/of';
    
    MockProductService.emitter = Observable.of(products);
    
    // don't call ngOnitInit. Use detectChanges instead
    fixture.detectChanges();
    
    // wait for observable subscription to resolve
    fixture.whenStable().then(() => {
      // do other stuff
      expect(comp.products).toBe(whatever)
    })
    

    You're also going to need to handle the getProducts method on the mock.

    As an aside, the EventEmitter is not really meant to be used for services. For that you should use Subjects. They're pretty much really the same thing, but it's still recommended not to use EventEmitter this way. Just google how to use Subjects. I'm sure there are a bunch of articles/post out there.