Home > OS >  Testing method inside a subscribe is called, when mocking an observable
Testing method inside a subscribe is called, when mocking an observable

Time:11-04

In my test 'should emit action complete', I'm trying to test that the emit function of 'bookActionCompleted' is called when the 'OnBookActionClick' method is called. This emit function is called inside a method passed into the subscribe function of a void observable returned from my service methods.

Unfortunately the code passed into the subscribe method on the component is never called via running my tests. In these tests I'm mocking the response of 'publishBook' and 'unpublishBook' by returning of(), I suspect this might be the cause of the issue, however I'm at a complete loss of how to proceed.

Component

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

import { Library, LibraryBook } from '../../models';
import { LibraryService } from '../../services';

@Component({
    selector: 'librarycatalogue-books-view',
    templateUrl: './books-view.component.html',
    styleUrls: ['./books-view.component.scss']
})
export class BooksViewComponent {
    @Input() library!: Library;
    @Output() bookActionCompleted = new EventEmitter();

    constructor(private libraryService: LibraryService) {}

    onBookActionClick(book: LibraryBook) {
        const action = book.isPublished
            ? this.libraryService.unpublishBook(this.library.libraryId, book.bookId)
            : this.libraryService.publishBook(this.library.libraryId, book.bookId);
        action.subscribe(() => {
            this.bookActionCompleted.emit()
        });
    }
}

Service methods (which are being mocked)

publishBook(libraryId: string, bookId: string): Observable<void> {
    return this.http.put<void>(`${this.baseUrl}/${libraryId}/book/${bookId}/publish`, null);
}

unpublishBook(libraryId: string, bookId: string): Observable<void> {
    return this.http.put<void>(`${this.baseUrl}/${libaryId}/book/${bookId}/unpublish`, null);
}

Tests

import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { createSpyObject, SpyObject } from '@ngneat/spectator/jest';
import { of } from 'rxjs';

import { Library, LibraryBook } from '../../models';
import { LibraryService } from '../../services';
import { BooksViewComponent } from './books-view.component';

describe('BooksViewComponent', () => {
  let component: BooksViewComponent;
  let fixture: ComponentFixture<BooksViewComponent>;
  let libraryService: SpyObject<LibraryService>;

  beforeEach(async () => {

    libraryService = createSpyObject(LibraryService);
    libraryService.publishBook.mockReturnValue(of());
    libraryService.unpublishBook.mockReturnValue(of());

    await TestBed.configureTestingModule({
      declarations: [ BooksViewComponent ],
      imports: [HttpClientTestingModule],
      providers: [
        { provide: LibraryService, useValue: libraryService }
      ]
    })
    .compileComponents();
  });

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

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  describe('On book action click', () => {
    let library: Library;
    let publishedBook: LibraryBook;
    let unpublishedBook: LibraryBook;

    beforeEach(() => {
      library = {  libraryId: '1',
                name: 'New York Library',
                books: [
                { book: 1, bookId: '1', isPublished: false},
                { book: 2, bookId: '2', isPublished: true }
              ]
            };

            publishedBook = library.books.filter(x => x.isPublished)[0];
            unpublishedBook = library.books.filter(x => !x.isPublished)[0];

      component.library = library;
      jest.spyOn(component.bookActionCompleted, 'emit');
    });
    
    describe('If item is published', () => {
      it('should be unpublished', () => {
        component.onBookActionClick(publishedBook);

        expect(libraryService.unpublishBook).toHaveBeenCalled();
      })

      it('should emit action complete', () => {
        component.onBookActionClick(publishedBook);
        expect(component.bookActionCompleted.emit).toHaveBeenCalledTimes(1);
      })
    });

  });
});

CodePudding user response:

You could subscribe to your bookActionCompleted EventEmitter and terminate the test with the help of the 'done' function paramter passed to the jest function.

it('should emit action complete', (done) => {
    // test will be green if done cb is called:
    component.bookActionCompleted.subscribe(done);
    component.onBookActionClick(publishedBook);
})

CodePudding user response:

I've just stumbled on the answer somewhat by accident. Changing the mock to return of(undefined) instead of of() has made the emit method fire.

No idea why this is the case, would be curious to know if anyone has the answer.

Before (not working)

libraryService.publishBook.mockReturnValue(of());
libraryService.unpublishBook.mockReturnValue(of());

After (working)

libraryService.publishBook.mockReturnValue(of(defined));
libraryService.unpublishBook.mockReturnValue(of(defined));
  • Related