5

I'm writing a unit test for a component that makes a call to a service OnInit. If the response is a 'success' one action taken and another for an 'error'.

What is the best way to test both cases? I've created a simplified version of the component and unit test. Something that I could easily test against in both cases.

I've attempted to implement the solution here but I must be off on the implementation. I've also attempted to throw an error as you will see in the spec and comments.

Component

@Component({
  selector: 'app-observer-throw-unit-test',
  template: '<p>{{ data }}</p>'
})
export class ObserverThrowUnitTestComponent implements OnInit {
    public data: string;

    constructor(private _observerThrowService: ObserverThrowService) { }

    ngOnInit() {
        this._observerThrowService.getData().subscribe(
            (data) => {
                this.data = data;
            },
            (error) => {
                this.redirect()
            }
        )
    }

    redirect() {
        this.data = "Redirecting...";
    }

}

Spec

const data: string = "Lorem ipsum dolor sit amet.";

const ObserverThrowServiceStub = {
  error: false,
  getData() {
    return Observable.create((observer) => {
      if(this.error) {
        observer.error(new Error());
      } else {
        observer.next(data);
      }
      observer.complete();
    })
  }
}

describe('ObserverThrowUnitTestComponent', () => {
  let component: ObserverThrowUnitTestComponent;
  let fixture: ComponentFixture<ObserverThrowUnitTestComponent>;
  let _observerThrowService: ObserverThrowService;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ ObserverThrowUnitTestComponent ],
      providers: [
        { provide: ObserverThrowService, useValue: ObserverThrowServiceStub },
      ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(ObserverThrowUnitTestComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
    _observerThrowService = TestBed.get(ObserverThrowService);
  });

  it('should set "data" to "Lorem ipsum dolor sit amet." on success', () => {
      expect(component.data).toEqual("Lorem ipsum dolor sit amet.");
  });

  it('should set "data" on "Redirecting..." on error',() => {
    ObserverThrowServiceStub.error = true;
    // spyOn(_observerThrowService, "getData").and.returnValue(Observable.throw("error")); // This did not work and returned : TypeError: undefined is not a constructor (evaluating 'Observable_1.Observable.throw("error")') in src/test.ts
    spyOn(_observerThrowService, "getData")
    expect(component.data).toEqual("Redirecting...");
  });

  it('should set "data" on "Redirecting..." on error',() => {
    // This works after setting error to true in the previous test
    spyOn(_observerThrowService, "getData")
    expect(component.data).toEqual("Redirecting...");
  });

});
bmd
  • 1,231
  • 3
  • 15
  • 23

4 Answers4

13

I would create two mocks - one that throws an error:

class ObserverThrowServiceStub {
    getData() {
        return Observable.throw(new Error('Test error'));
    }
}

and one that returns successfully.

class ObserverSuccessServiceStub {
    getData() {
        return Observable.from(data);
    }
}

Then, instead of providing the same mock service to all tests, you provide the failing/successful mock service appropriately depending on the test in question (obviously you'll need to move your module configuration into a configurable method that you call from within each test rather than in the .beforeEach() code.

There is a really nice article on testing Angular services using Observables here (which uses exactly this model): http://www.zackarychapple.guru/angular2/2016/11/25/angular2-testing-services.html

The Fabio
  • 5,369
  • 1
  • 25
  • 55
Steve Land
  • 4,852
  • 2
  • 17
  • 36
  • This is exactly what I was looking for. The method and link you included were spot on. Thank you! – bmd Jul 14 '17 at 08:11
3
someObj.ObservableReturninFunction().subscribe(
    (obj)=> {
        conosle.log(obj.message);
    },
    (err)=>{
        console.log(err.message);
    }
});
when success;
    SpyOn(someObj,"ObservableReturninFunction").and.returnValue(
            Observable.of({message: "something"}));
when erro:
    SpyOn(someObj,"ObservableReturninFunction").and.returnValue(
        Observable.throw({message: "some-error"}));
anees
  • 334
  • 1
  • 6
1

You can use Jasmine Spy to spyOn your mock service class's method which returns an Observable. More detail is here: Jasmine - how to spyOn instance methods

0

I would add a public error flag in the stub. Then I can create the stub once in beforeEach() and just update the error flag in each test case to decide which version of getData I want to use.

class ObserverServiceStub {
  public error = false;
  getData() {
    if (this.error) {
      return Observable.throw(new Error('Test error'));
    } else {
      return Observable.from(data);
    }
  }
}
Aditya Mittal
  • 1,681
  • 16
  • 12