Yauheni Pozdnyakov
Yauheni Pozdnyakov

Reputation: 203

Testing Observable with subscribe in a function

Here is the testbed setup. Mocking and providing everything needed:

 let component: PageUploadContainer;
  let store;
  let route;
  let fixture: ComponentFixture<PageUploadContainer>;
  const sanitizer = jasmine.createSpyObj('sanitizer', ['bypassSecurityTrustResourceUrl']);
  sanitizer.bypassSecurityTrustResourceUrl.and.callFake(url => url);
  const mockTranslate = {
    instant: label => label,
  };

  beforeEach(() => {
    store = createMockStore<AppState>(reducers);
    route = new MockActivatedRoute();
  });

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [translationModule],
      declarations: [
        PageUploadContainer,
        MockTranslatePipe,
        MockTranslateCutPipe,
      ],
      schemas: [NO_ERRORS_SCHEMA],
      providers: [
        FormBuilder,
        { provide: DomSanitizer, useValue: sanitizer },
        { provide: Store, useValue: store },
        { provide: ActivatedRoute, useValue: route },
        { provide: TranslateService, useFactory: () => mockTranslate },
      ],
    }).compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(PageUploadContainer);
    component = fixture.componentInstance;
    component.mappingTriggered$ = Observable.of(false);
  });

I have the following code, I set up me tests with testBed:

onGoTo(uploadStep: string) {
    if (uploadStep === NAVIGATION.DEFAULT) {
      this.store.dispatch(new ReplaceSelectedCompanies([]));
      this.companyIds$.filter(isEmpty)
        .take(1)
        .subscribe(() => {
          this.store.dispatch(new Navigate({ direction: uploadStep}));
        });
    } else {
      this.store.dispatch(new Navigate({ direction: uploadStep}));
    }
  }

And my test. This test fails due to new Navigate({ direction: uploadStep}) was not called due to it's asynchronous nature

it('if navigation default and companyIds empty should call navigate', () => {
       component.companyIds$ = Observable.of(['123', '234']);
      component.onGoTo(NAVIGATION.DEFAULT);
      expect(store.dispatch).toHaveBeenCalledWith(new ReplaceSelectedCompanies([]));
      expect(store.dispatch).toHaveBeenCalledWith(new Navigate({ direction: NAVIGATION.DEFAULT }));
    });

Can you help me how to test such methods?

Upvotes: 2

Views: 9362

Answers (2)

christo8989
christo8989

Reputation: 6826

Although I am not certain, you might need to just import the async method provided by angular.

General Example

it("description", async( /* function */ ));

Your Code

import { async } from "@angular/core/testing";

it('if navigation default and companyIds empty should call navigate', async(() => {
  component.companyIds$ = Observable.of(['123', '234']);
  component.onGoTo(NAVIGATION.DEFAULT);
  expect(store.dispatch)
    .toHaveBeenCalledWith(new ReplaceSelectedCompanies([]));
  expect(store.dispatch)
    .toHaveBeenCalledWith(new Navigate({ direction: NAVIGATION.DEFAULT }));
}));

Recommendations

First, I would watch this video about angular 6 asynchronous unit tests. The whole unit test series is good too.

You have a lot of custom-ly mocked classes so I can't comment much on that. I do recommend using RouterTestingModule.withRoutes({ /* test routes */ }). Here's another video in that series that talks about the RouterTestingModule.

Then, you could always get the mocked router like router = TestBed.get(Router) or other objects created by the RouterTestingModule.

Upvotes: 2

ggradnig
ggradnig

Reputation: 14149

You can create asynchronous Jasmine tests by calling done() after your tested code has executed completely. You get the done function passed as an argument from the test spec, like that:

it('if navigation default and companyIds empty should call navigate', (done) => {
   ...
});

However, you wouldn't know in your test code when the code has executed completely. I'd suggest returning a Promise from `onGoTo. It could look like that:

onGoTo(uploadStep: string): Promise<void> {
    if (uploadStep === NAVIGATION.DEFAULT) {
      this.store.dispatch(new ReplaceSelectedCompanies([]));
      return new Promise(resolve => this.companyIds$.filter(isEmpty)
        .take(1)
        .subscribe(() => {
          this.store.dispatch(new Navigate({ direction: uploadStep}));
          resolve();
        }));
    } else {
      this.store.dispatch(new Navigate({ direction: uploadStep}));
      return Promise.resolve();
    }
  }

Then, modify your test code like that:

it('if navigation default and companyIds empty should call navigate', (done) => {
   component.companyIds$ = Observable.of(['123', '234']);
  component.onGoTo(NAVIGATION.DEFAULT).then(() => {
    expect(store.dispatch).toHaveBeenCalledWith(new ReplaceSelectedCompanies([]));
    expect(store.dispatch).toHaveBeenCalledWith(new Navigate({ direction: NAVIGATION.DEFAULT }));
    done()});
});

Upvotes: 0

Related Questions