balteo
balteo

Reputation: 24679

Issue with jasmine expectations not being tested/evaluated because of simulated mock error

I am trying to unit test a angular 2 component and simulate an error (400/bad request) from the http backend.

Here is my code:

describe('Component: UserAccountActivationComponent', () => {

  let fixture: ComponentFixture<UserAccountActivationComponent>;
  let userAccountActivationComponent: UserAccountActivationComponent;
  const observableMock = Observable.of('Some Observable');

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      providers: [
        MockBackend,
        BaseRequestOptions,
        {
          provide: Http,
          useFactory: (backend, options) => new Http(backend, options),
          deps: [MockBackend, BaseRequestOptions]
        },
        {
          provide: ActivatedRoute, useClass: RouteMock
        },
      ],
      imports: [AppModule, HttpModule]
    })
      .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(UserAccountActivationComponent);
    userAccountActivationComponent = fixture.componentInstance;
  });

  it('should not signin user nor navigate to dashboard if account is not activated', fakeAsync(inject(
    [UserAccountService, SessionSigninService, Router, MockBackend],
    (userAccountService: UserAccountService, signinService: SessionSigninService, router: Router, mockBackend: MockBackend) => {

      const opts = {
        type: ResponseType.Error,
        status: 400
      };
      const responseOpts = new ResponseOptions(opts);

      mockBackend.connections.subscribe(
        (connection: MockConnection) => {
          connection.mockError(new MockError(responseOpts));
        });

      const activateSpy = spyOn(userAccountService, 'activateAccount').and.callThrough();
      const signinSpy = spyOn(signinService, 'signinByUserAccountToken').and.returnValue(observableMock);
      const navigateSpy = spyOn(router, 'navigate').and.returnValue(observableMock);

      fixture.detectChanges();
      tick();

      expect(activateSpy).toHaveBeenCalled();
      expect(signinSpy).not.toHaveBeenCalled();
      expect(navigateSpy).not.toHaveBeenCalled();
    })));

});

class MockError extends Response implements Error {
  name: any;
  message: any;
}

class RouteMock {
  readonly params = Observable.of({userAccountToken: 'a-token'});
}

I am simulating an error occurring in the userAccountService.activateAccount method. I just want to check that neither signinService.signinByUserAccountToken nor the router.navigate are called. This is what I am trying to do with the jasmine expectations:

  expect(activateSpy).toHaveBeenCalled();
  expect(signinSpy).not.toHaveBeenCalled();
  expect(navigateSpy).not.toHaveBeenCalled();

However, I the test is stopped by the simulated error:

Chrome 56.0.2924 (Mac OS X 10.12.3) Component: UserAccountActivationComponent should not signin user nor navigate to dashboard if account is not activated FAILED
    Error: Error in :0:0 caused by: Response with status: 400 null for URL: null
        at ViewWrappedError.ZoneAwareError (webpack:///~/zone.js/dist/zone.js:811:0 <- src/test.ts:142448:33)
        at ViewWrappedError.BaseError [as constructor] (webpack:///~/@angular/core/src/facade/errors.js:22:0 <- src/test.ts:36304:16)
        at ViewWrappedError.WrappedError [as constructor] (webpack:///~/@angular/core/src/facade/errors.js:87:0 <- src/test.ts:36369:16)
        at new ViewWrappedError (webpack:///~/@angular/core/src/linker/errors.js:77:0 <- src/test.ts:70405:16)
        at proxyClass.DebugAppView._rethrowWithContext (webpack:///~/@angular/core/src/linker/view.js:653:0 <- src/test.ts:111685:23)
        at proxyClass.DebugAppView.detectChanges (webpack:///~/@angular/core/src/linker/view.js:626:0 <- src/test.ts:111658:18)
        at ViewRef_.detectChanges (webpack:///~/@angular/core/src/linker/view_ref.js:170:0 <- src/test.ts:71336:20)
        at ComponentFixture._tick (webpack:///~/@angular/core/bundles/core-testing.umd.js:196:0 <- src/test.ts:15653:36)
        at webpack:///~/@angular/core/bundles/core-testing.umd.js:210:45 <- src/test.ts:15667:53
        at ZoneDelegate.invoke (webpack:///~/zone.js/dist/zone.js:242:0 <- src/test.ts:141879:26)
        at ProxyZoneSpec.onInvoke (webpack:///~/zone.js/dist/proxy.js:79:0 <- src/test.ts:98996:39)
        at ZoneDelegate.invoke (webpack:///~/zone.js/dist/zone.js:241:0 <- src/test.ts:141878:32)
        at Object.onInvoke (webpack:///~/@angular/core/src/zone/ng_zone.js:271:0 <- src/test.ts:38049:37)
        at ZoneDelegate.invoke (webpack:///~/zone.js/dist/zone.js:241:0 <- src/test.ts:141878:32)
        at Zone.run (webpack:///~/zone.js/dist/zone.js:113:0 <- src/test.ts:141750:43)

FYI, here is the component under test:

@Component({
  templateUrl: './useraccount-activation.component.html'
})
export class UserAccountActivationComponent implements OnInit {

  constructor(private userAccountService: UserAccountService,
              private signinService: SessionSigninService,
              private router: Router,
              private route: ActivatedRoute) {
  }

  ngOnInit() {
    this.route.params
      .take(1)
      .pluck('userAccountToken')
      .switchMap((userAccountToken: string) =>
        this.userAccountService.activateAccount(userAccountToken)
          .switchMapTo(this.signinService.signinByUserAccountToken(userAccountToken))
      )
      .subscribe(() => this.router.navigate(['/dashboard']));
  }
}

edit 1: Unfortunately, changing to:

  mockBackend.connections.subscribe(
    (connection: MockConnection) => {
      responseOpts.url = connection.request.url;
      connection.mockError(new MockError(responseOpts));
    });

Still causes the following error:

'HttpClient error: ', MockError{_body: null, status: 400, ok: false, statusText: null, headers: null, type: 3, url: '/api/useraccount/activate/a-valid-token'}

    Error: Error in :0:0 caused by: Response with status: 400 null for URL: /api/useraccount/activate/a-valid-token
        at Error.ZoneAwareError (webpack:///~/zone.js/dist/zone.js:958:0 <- src/test.ts:143513:33)
        at ZoneAwareError (webpack:///~/zone.js/dist/zone.js:955:0 <- src/test.ts:143510:35)
        at wrappedError (webpack:///~/@angular/core/src/error_handler.js:144:21 <- src/test.ts:36697:34)
        at viewWrappedError (webpack:///~/@angular/core/src/linker/errors.js:65:21 <- src/test.ts:70785:125)
        at proxyClass.DebugAppView._rethrowWithContext (webpack:///~/@angular/core/src/linker/view.js:656:0 <- src/test.ts:112336:111)
        at proxyClass.DebugAppView.detectChanges (webpack:///~/@angular/core/src/linker/view.js:629:0 <- src/test.ts:112309:18)
        at ViewRef_.detectChanges (webpack:///~/@angular/core/src/linker/view_ref.js:172:0 <- src/test.ts:71715:20)
        at ComponentFixture._tick (webpack:///~/@angular/core/bundles/core-testing.umd.js:196:0 <- src/test.ts:15805:36)
        at webpack:///~/@angular/core/bundles/core-testing.umd.js:210:45 <- src/test.ts:15819:53
        at ZoneDelegate.invoke (webpack:///~/zone.js/dist/zone.js:330:0 <- src/test.ts:142885:26)
        at ProxyZoneSpec.onInvoke (webpack:///~/zone.js/dist/proxy.js:79:0 <- src/test.ts:99788:39)
        at ZoneDelegate.invoke (webpack:///~/zone.js/dist/zone.js:329:0 <- src/test.ts:142884:32)
        at Object.onInvoke (webpack:///~/@angular/core/src/zone/ng_zone.js:273:0 <- src/test.ts:38516:37)
        at ZoneDelegate.invoke (webpack:///~/zone.js/dist/zone.js:329:0 <- src/test.ts:142884:32)
        at Zone.run (webpack:///~/zone.js/dist/zone.js:126:0 <- src/test.ts:142681:43)

Upvotes: 0

Views: 426

Answers (1)

Teddy Sterne
Teddy Sterne

Reputation: 14221

It looks to me like you are getting the exception because the Url is not specified. Try setting the url on the options like so:

let responseOpts = new ResponseOptions(opts);

mockBackend.connections.subscribe(
  (connection: MockConnection) => {
    responseOpts.url = connection.request.url;
    connection.mockError(new MockError(responseOpts));
});

Edit 1

The issue you are seeing might be because the error is not being caught anywhere and so it is bubbling up to zone. Try adding an error catch to your subscribe to handle exceptions or squelch the error, (error) => {}.

this.route.params
  .take(1)
  .pluck('userAccountToken')
  .switchMap((userAccountToken: string) =>
    this.userAccountService.activateAccount(userAccountToken)
      .switchMapTo(this.signinService.signinByUserAccountToken(userAccountToken))
  )
  .subscribe(() => this.router.navigate(['/dashboard']), (error) => {});

Upvotes: 1

Related Questions