1

I'm trying to write some unit test (Jasmine) for my Angular 11 Component. In ngOnInit() it calls two http ajax functions from the service that return data from the database. Both return a Subscription (rxjs), and I chained them with pipe() and switchMap().

It's something like this:

ngOnInit(): void {
    const configID = 12345;
    this.service.getDataOne(configID)
        .pipe(
            switchMap(() => {
                return this.service.getDataTwo(configID);
            })
        )
        .subscribe(res => {
              if(res.SUCCESS) {
                    this.dataSource.data = res.DATA.RESULT;
              }
              else {
                  this.error = res.ERROR;
              }
         });
}

In my spec.ts:

describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
const serviceSpy = jasmine.createSpyObj('MyService', [ 'getDataOne', 'getDataTwo' ]);
const mockSuccessData1 = {
    SUCCESS: true
    , DATA: 'mock result 1';
}

const mockSuccessData2 = {
    SUCCESS: true 
    , DATA: 'mock result 2';
}

beforeEach(waitForAsync(() => {
    TestBed.configureTestingModule({
        declarations: [ MyComponent ]
        , imports: [ ]
        , providers : [
            { provide: MyService, useValue: serviceSpy }
        ]
    }).compileComponents();

    fixture = TestBed.createComponent(MyComponent);
    component = fixture.componentInstance;
    serviceSpy.getDataOne.and.returnValue(of(mockSuccessData1));

    fixture.detectChanges();
}));

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

});

What's the proper way to test this component and mock the subscriptions? I tried serviceSpy.getDataOne.and.returnValue(of(mockSuccessData1 )); But I got error "Failed: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable."

Thank you in advance.

Telly Ipock
  • 133
  • 3
  • 12

1 Answers1

1

I would recommend to create a mock service and then use it under providers as UseClass

Create a mock service like below:

export class MockMyService {

    getDataOne() {
        return of({
            SUCCESS: true,
            DATA: 'mock result 1'
        })
    }

    getDataTwo() {
        return of({
            SUCCESS: true
            , DATA: 'mock result 2'
        })


    }
}

and in your test file

beforeEach(waitForAsync(() => {
    TestBed.configureTestingModule({
        declarations: [ MyComponent ]
        , imports: [ ]
        , providers : [
            { provide: MyService, useClass: MockMyService }
        ]
    }).compileComponents();

    fixture = TestBed.createComponent(MyComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
}));

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

mak15
  • 367
  • 2
  • 12
  • I tried and am getting the following error: "TypeError: Cannot read properties of undefined (reading 'pipe')". I think it's still running the code in the component. And I thought I'm supposed to create a Spy for the service: https://angular.io/guide/testing-components-scenarios#testing-with-a-spy. I don't have trouble testing with a asyn. I just don't know how to test the stream with pipe and switchMap, which are in the code I'm trying to test. Thanks. – Telly Ipock Oct 08 '21 at 12:12
  • Minor correction @telly Ipock, I forgot to add return in the mock service. Can you try to add that? – mak15 Oct 08 '21 at 15:10
  • You do not need to worry about the rxjs operators, if your mocks are returning the data, it will apply all the operators and give you the final output. I have updated the mock service, can you check with those changes once. – mak15 Oct 08 '21 at 15:27
  • Hi. I think your code worked. Thank you. But I don't understand how that's different from my original code `const serviceSpy = jasmine.createSpyObj('MyService', [ 'getDataOne', 'getDataTwo' ]); ... serviceSpy.getDataOne.and.returnValue(of(mockSuccessData1));...` ? And if I export all these SUCCESS data with the mock service, how do I test if the calls fail? Thanks. – Telly Ipock Oct 11 '21 at 20:33
  • @TellyIpock to test error cases, you can spyOn on the mock service and then throw an error to test those cases e.g. spyOn(MockMyService.prototype, 'getDataOne').and.returnValue(throwError({error: 'failed'})); – mak15 Oct 12 '21 at 06:40
  • 1
    Thank you for your help. I appreciate it. And I found another way that worked for me using jasmine's createSpyObj: `const mockService = jasmine.createSpyObj('MockMyService', {getDataOne: of(mockSuccessData1), getDataTwo: of(mockSuccessData2)});` It's very similar to what I was doing, but I guess returning to observables with "of" did the trick. – Telly Ipock Oct 12 '21 at 12:53
  • Great, I learnt a new way as well. Thanks @Telly Ipock – mak15 Oct 12 '21 at 15:46