1
votes

I'm trying to test a component that on her ngOnInit subscribe to observable in a service, and change the behavior of the component according to the value got from the observable. I reduced my problem to these pieaces of code.

the component:

import { Component, OnInit } from '@angular/core';
import { AppService } from './app.service';

@Component({
  selector: 'app-root',
  template: `
  <div>
    <h3>currentValue is {{currentValue}}</h3>
    <div>
      <button (click)="changeTo(true)">true</button>
      <button (click)="changeTo(false)">false</button>
    </div>
  </div>
  `,
  styleUrls: ['./app.component.less']
})
export class AppComponent implements OnInit {
  public currentValue: boolean;

  constructor(
    private appService: AppService
  ) { }

  ngOnInit() {
    this.appService.getObs().subscribe(newValue => this.currentValue = newValue);
  }

  changeTo(value: boolean) {
    this.appService.setObs(value);
  }
}

the service:

import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';

@Injectable({
    providedIn: 'root'
})
export class AppService {
    private obs = new BehaviorSubject<boolean>(false);

    getObs(): Observable<boolean> {
        return this.obs.asObservable();
    }

    setObs(value: boolean): void {
        this.obs.next(value);
    }
}

the test:

import { TestBed, async, ComponentFixture } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { AppService } from './app.service';
import { of } from 'rxjs';

describe('AppComponent', () => {
  let mockAppService = jasmine.createSpyObj(['setObs', 'getObs']);

  let app: AppComponent
  let fixture: ComponentFixture<AppComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ],
      providers: [
        { provide: AppService, useValue: mockAppService }
      ]
    }).compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(AppComponent);
    mockAppService.getObs.and.returnValue(of(false));
    app = fixture.debugElement.componentInstance;
    fixture.detectChanges();
  });

  it('should create the app', () => {

    expect(app).toBeTruthy(); // pass
  });

  it('should set currentValue to false', () => {

    expect(app.currentValue).toBe(false); // pass
  });

  it('should change currentValue to true', () => 
    mockAppService.// some how to do next(true)
    fixture.detectChanges();

    expect(app.currentValue).toBe(true); // 
  });
});

I expect to have an easy way to control the values that the mockObservable returned. I want to change the value between tests and test each of the options.

I saw the option of jasmine-marbles but it looks to complicated for such an issue. Does any one know an easy way to do that?

EDIT: I want to have the ability to call next() on the observale, without calling setObs (lets say it's private, actually in my app I don't have setObs function, the call for next() is in more complex function)

1

1 Answers

0
votes

Your component calls getObs() in ngOnInit(). Your test calls fixture.detectChanges() in the beforeEach block. And the first call to detectChanges() causes ngOnInit() to be called.

So changing the behavior of getObs() in the test won't change anything. It's too late. The component has already called it and subscribed before that, when the beforeEach block was executed.

Move the call to the first fixture.detectChanges() inside the test function, and make sure to stub the service (tell what getObs() must return) before calling fixture.detectChanges().