2

I've just started writing unit tests for my Angular project, and I'm experiencing some problems with testing if the language is set.

This is the code of my app.component.ts:

ngOnInit() {
    this.translateService.setDefaultLang('en');

    this.translateService.onLangChange.subscribe((langChangeEvent: LangChangeEvent) => {
        this.localStorageService.setItem(LocalStorageService.languageKey, langChangeEvent.lang);
    });
    this.translateService.use('en');  <---- The subscribe callback should be called
}

This is the code of my app.component.spec.ts:

  describe('ngOnInit', () => {
    it('should set default language as en', () => {
      const translateService = fixture.debugElement.injector.get(TranslateService);
      spyOn(translateService, 'setDefaultLang');
      component.ngOnInit();
      expect(translateService.setDefaultLang).toHaveBeenCalledWith('en');
  }); <----- This part of test is successful



    it('should set new lang key to localStorageService on TranslateService.onLangChange observable emit', () => {
      const localStorageService = fixture.debugElement.injector.get(LocalStorageService);
      spyOn(localStorageService, 'setItem');
      component.ngOnInit();
      translateService.use('de');
      expect(localStorageService.setItem).toHaveBeenCalledWith(LocalStorageService.languageKey, 'de');
  });<-------------This test throws error (see below)

}); 

I am getting this error:

enter image description here

I tested in the app that the subscribe callback gets executed, however the test says it doesn't. Am I totally misunderstanding the whole unit testing concept here? What am I doing wrong?

EDIT:

As requested by dmcgrandle - posting the full app.component.spec.ts:

import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
import {TranslateModule ,TranslateService, LangChangeEvent} from "@ngx-translate/core";
import {LocalStorageService} from "./core/local-storage.service";
import {OAuthService, UrlHelperService} from "angular-oauth2-oidc";
import {HttpClientModule} from "@angular/common/http";
import {ConfigurationService} from "./core/configuration.service";
import {MockConfigurationService} from './testing/mock-services/configuration.service.mock';
import { of } from 'rxjs/observable/of';
import * as fastClick from 'fastclick';

describe('AppComponent', () => {
  const mockFastClick = jasmine.createSpyObj('fastClick', ['attach']);
  let component : AppComponent;
  let fixture;
  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ],
      imports: [
        RouterTestingModule.withRoutes([]),
        TranslateModule.forRoot(),
        HttpClientModule

      ],
      providers:[
        TranslateService,
        LocalStorageService,
        OAuthService,
        UrlHelperService,
        { provide: fastClick, useValue: mockFastClick },
        { provide: ConfigurationService, useValue: MockConfigurationService }
      ]
    }).compileComponents();
    fixture = TestBed.createComponent(AppComponent);
    component = fixture.debugElement.componentInstance;
  });

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

  describe('ngOnInit', () => {
    it('should call fastClick.attach', () => {
        spyOn(fastClick, 'attach');
        component.ngOnInit();
        expect(fastClick.attach).toHaveBeenCalledWith(document.body, null);
    });
    it('should have loaded config', () => {
      fixture.detectChanges();
      const appConfig = fixture.debugElement.injector.get(ConfigurationService);
      expect(component.appConfig).toEqual(appConfig.config);
    });
    it('should set default language as en', () => {
      const translateService = fixture.debugElement.injector.get(TranslateService);
      spyOn(translateService, 'setDefaultLang');
      component.ngOnInit();
      expect(translateService.setDefaultLang).toHaveBeenCalledWith('en');
  });
   it('should set new lang key to localStorageService on TranslateService.onLangChange observable emit', () => {
      const localStorageService = fixture.debugElement.injector.get(LocalStorageService);
      spyOn(localStorageService, 'setItem');
      component.ngOnInit();
      expect(localStorageService.setItem).toHaveBeenCalledWith(LocalStorageService.languageKey, 'de');
  });

});

});
GeForce RTX 4090
  • 3,091
  • 11
  • 32
  • 58
  • Please post your entire `app.component.spec.ts` file. :) – dmcgrandle Dec 19 '18 at 20:53
  • @dmcgrandle See the edit. – GeForce RTX 4090 Dec 20 '18 at 08:34
  • I'm not familiar with `this.translateService.onLangChange` - but it appears to be an observable you can subscribe to which will emit a new langChange event when the language is changed ... do you know if this is a synchronous Observable? If it is asynchronous, you may need to wrap this spec in an async() wrapper. – dmcgrandle Dec 20 '18 at 09:20
  • Looks like there are a lot of issues trying to mock `ngx-translate` in testing. See https://github.com/ngx-translate/core/issues/636 – dmcgrandle Dec 20 '18 at 09:28

1 Answers1

7

Not sure if this will be helpful or not, but I will post this as an answer in case it is, unfortunately I don't have a way of testing this myself.

I would approach this by trying to mock the TranslateService in your providers array with a mock. Perhaps something like this (but mock all the methods needed):

class TranslateServiceStub {
    setDefaultLang(lang: string) { }
    use(lang: string) { }
    get onLangChange() { return of({lang: 'en'}) }
}

And in your providers array to TestBed change

TranslateService,

to

{ provide: TranslateService, useClass: TranslateServiceStub },

Then in your spec you can change the return from the getter

it('should set new lang key to localStorageService on TranslateService.onLangChange observable emit', () => {
      const localStorageService = fixture.debugElement.injector.get(LocalStorageService);
      const translateService = fixture.debugElement.injector.get(TranslateService);
      spyOnProperty(translateService, 'onLangChange', 'get').and.returnValue(of({lang: 'de'}));
      spyOn(localStorageService, 'setItem');
      component.ngOnInit();
      expect(localStorageService.setItem).toHaveBeenCalledWith(LocalStorageService.languageKey, 'de');
  });

As I mentioned in the comments above though, apparently this is a particularly hard service to mock.

Good luck. :)

dmcgrandle
  • 5,934
  • 1
  • 19
  • 38