1

I am writing Unit Test cases for a function in Angular7 template . It is a login component and the login function has router.navigate inside the http request, to route to the dashboard on correct login. But I am getting error -

Error: Expected spy navigate to have been called with [ [ '/ProjectData/MasterSequence' ] ] but it was never called. at stack (http://localhost:9876/absolute/home/hp/Downloads/ICICI/ICICI_UI/node_modules/jasmine-core/lib/jasmine-core/jasmine.js?0b1eaf7a13cae32191eadea482cfc96ae41fc22b:2455:17) at buildExpectationResult (http://localhost:9876/absolute/home/hp/Downloads/ICICI/ICICI_UI/node_modules/jasmine-core/lib/jasmine-core/jasmine.js?0b1eaf7a13cae32191eadea482cfc96ae41fc22b:2425:14) at Spec.expectationResultFactory (http://localhost:9876/absolute/home/hp/Downloads/ICICI/ICICI_UI/node_modules/jasmine-core/lib/jasmine-core/jasmine.js?0b1eaf7a13cae32191eadea482cfc96ae41fc22b:901:18) at Spec.addExpectationResult (http://localhost:9876/absolute/home/hp/Downloads/ICICI/ICICI_UI/node_modules/jasmine-core/lib/jasmine-core/jasmine.js?0b1eaf7a13cae32191eadea482cfc96ae41fc22b:524:34) at Expectation.addExpectationResult (http://localhost:9876/absolute/home/hp/Downloads/ICICI/ICICI_UI/node_modules/jasmine-core/lib/jasmine-core/jasmine.js?0b1eaf7a13cae32191eadea482cfc96ae41fc22b:845:21) at Expectation.toHaveBeenCalledWith (http://localhost:9876/absolute/home/hp/Downloads/ICICI/ICICI_UI/node_modules/jasmine-core/lib/jasmine-core/jasmine.js?0b1eaf7a13cae32191eadea482cfc96ae41fc22b:2369:12) at UserContext. (http://localhost:9876/src/app/authentication/login2/login2.component.spec.ts?:208:38)

app.component.html

loginData(value) {
    this.username = value.username;
    this.password = value.password;
    this.dataObj = {
      'username': this.username,
      'password': this.password
    }
    this.loginService.login(this.dataObj).then((data) => {
      console.log("data", data);
      this.response = data;
      console.log("message", this.response.message);
      if (this.response.message == "Login Successful.") {
        this.router.navigate(['/ProjectData/MasterSequence'])
      }
      else {
        this.toastr.error("UserName or Password is incorrect");
      }
    })

app.component.spec.ts

describe('Login2Component', () => {
    let comp: Login2Component;
    let fixture: ComponentFixture<Login2Component>;
    let de: DebugElement;
    let el: HTMLElement;
    beforeEach(async(() => {
        mockRouter = { navigate: jasmine.createSpy('navigate') };
        TestBed.configureTestingModule({
            declarations: [Login2Component, MasterSequenceComponent],

            imports: [
                BrowserModule,
                FormsModule,
                RouterModule,
                ReactiveFormsModule,
                RouterTestingModule,
                HttpClientModule,

                [
                    RouterTestingModule.withRoutes([
                        { path: '/ProjectData/MasterSequence', 
                          component: MasterSequenceComponent }
                    ])
                ]
            ],
            schemas: [CUSTOM_ELEMENTS_SCHEMA],

            providers: [{ provide: ToastrService, useValue: ToastrService }, Login2Service]
        })
            .compileComponents()
            .then(() => {
                //Login2Component
                fixture = TestBed.createComponent(Login2Component);

                comp = fixture.componentInstance;

                de = fixture.debugElement.query(By.css('form'));
                el = de.nativeElement;
            });
    }));

    it('should redirect the user to "login form" component when button is clicked', () => {
        const router = TestBed.get(Router);
        const spy = spyOn(router, 'navigate');
        fixture.detectChanges();

        const button = fixture.debugElement.query(By.css('form[id=loginform]'));

        button.triggerEventHandler('click', null);

        let userN = comp.addLoginData.controls['username'];
        userN.setValue('pallavi');
        let pass = comp.addLoginData.controls['password'];
        pass.setValue(1234);

        let value = {
            username: userN,
            password: pass
        };

        comp.loginData(value);

        expect(spy).toHaveBeenCalledWith(['/ProjectData/MasterSequence']);
    });
});

Reference -

Angular 2/4/6/7 - Unit Testing with Router

Further Error On using this code -

it('should redirect the user to "login form" component when button is clicked', () => {
    let value = {
         username: 'user123',
         password: 'pwd'
     };
     comp.username = '';
     comp.password = '';

     spyOn(comp.LoginService,'login').and.callThrough();

    comp.loginData(value);
     expect(comp.username).toBe('user123');
     expect(comp.password).toBe('pwd');
     expect(comp.LoginService.login).toHaveBeenCalledWith(value)

//expect(RouterMock.navigate).toHaveBeenCalledWith(['/ProjectData/MasterSe//quence']);
    });

ERROR: 'Unhandled Promise rejection:', HttpErrorResponse{headers: HttpHeaders{normalizedNames: Map{}, lazyUpdate: null, he aders: Map{}}, status: 0, statusText: 'Unknown Error', url: 'http://192.168.5.128:3002/api/user/login', ok: false, name: 'HttpErrorResponse', message: 'Http failure response for http://192.168.5.128:3002/api/user/login: 0 Unknown Error', error: ProgressEvent{isTrusted: true}}, '; Zone:', 'ProxyZone', '; Task:', 'Promise.then', '; Value:', HttpErrorResponse{headers: HttpHeaders{normalizedNames: Map{}, lazyUpdate: null, headers: Map{}}, status: 0, statusText: 'Unknown Error', url: 'http://192.168.5.128:3002/api/user/login', ok: false, name: 'HttpErrorResponse', message: 'Http failure response for http://192.168.5.128:3002/api/user/login: 0 Unknown Error', error: ProgressEvent{isTrusted: true}}, undefined

Techdive
  • 997
  • 3
  • 24
  • 49
  • Could you please provide the component and template associated with that test otherwise it's a bit hard trying to figure out why your test doesn't work as expected. – Erbsenkoenig Jul 26 '19 at 10:17
  • OK. Thanks. I will do that – Techdive Jul 26 '19 at 10:18
  • 1
    Well without seeing the html I can't really understand why you are triggering a button click. You might need to be waiting for an async job to finish, but that's just guessing. But one problem is definitely that you need to wait until the promise returned from the loginService actually resolves. You can use ```fakeAsync``` in your test block ```it``` and a ```tick()``` before you expect that your spy has been called. But whether this alone will fix your test, I cannot tell having not seen the template. Besides what is the reason you are using the real Login2Service? – Erbsenkoenig Jul 26 '19 at 11:59
  • @Techdive Any update on my suggestion as provided in the chat ? – Shashank Vivek Aug 07 '19 at 06:11

1 Answers1

1

I cant find code of loginService.login() but I am sure that it must be making some API call, so its a good practice to create a stub. something like:

export class LoginServiceStub{
  login(obj){
     return new Promise((resolve, reject) => {
         resolve("whatever_data_is_expected");
     });
   }
}

In spec file:

describe('Login2Component', () => {
  let RouterMock = {
    navigate: jasmine.createSpy('navigate')
  };

 beforeEach(async(() => {
        mockRouter  = { navigate: jasmine.createSpy('navigate') };
        TestBed.configureTestingModule({
        decleration: [ .... ],
        providers: [
             {provide: ToastrService, useValue: ToastrService }, // I am not sure why you have done "useValue" with same Service
             {provide: Login2Service, useClass: Login2ServiceStub },
             {provide: Router, useValue: RouterMock  }
             ], 
      // ... and other deceleration of Test Bed
  )};

})

and then in it block:

it('should redirect the user to "login form" component when button is clicked', () => {
       let value = {
            username: 'user123',
            password: 'pwd';
        };
        comp.username = '';
        comp.password = '';
        spyOn(comp.loginService,'login').and.callThrough();
        comp.loginData(value);
        expect(comp.username).toBe('user123');
        expect(comp.password).toBe('pwd');
        expect(comp.loginService.login).toHaveBeenCalledWith(value)
        expect(RouterMock.navigate).toHaveBeenCalledWith(['/ProjectData/MasterSequence']);
    });
});

I would also suggest you to read this collection of articles specifically written for Unit testing in Angular. You can find several links which will cover almost all basic testing scenarios.

pathe.kiran
  • 2,444
  • 1
  • 21
  • 27
Shashank Vivek
  • 16,888
  • 8
  • 62
  • 104
  • Hi Shashank, thanks for the reply. But i am getting error - Property 'LoginService' does not exist on type 'LoginComponent'. I guess ...it is not recognizing LoginService or loginService – Techdive Jul 30 '19 at 05:53
  • @Techdive : can u check if its public in constructor `constructor ( public LoginService )` etc etc – Shashank Vivek Jul 30 '19 at 08:35
  • After rectifying, I still get error - Expected spy navigate to have been called with [ [ '/ProjectData/MasterSequence' ] ] but it was never called. – Techdive Jul 31 '19 at 04:33
  • @ Shashank, And if i remove the lastline - i get error - ERROR: 'Unhandled Promise rejection:', HttpErrorResponse{headers: HttpHeaders{normalizedNames: Map{}, lazyUpdate: null, headers: Map{}}, status: 0, statusText: 'Unknown Error', url: 'http://192.168.5.128:3002/api/user/login', ok: false, name: 'HttpErrorResponse', message: 'Http failure response for http://192.168.5.128:3002/api/user/login: 0 Unknown Error', ... – Techdive Jul 31 '19 at 04:36
  • ....error: ProgressEvent{isTrusted: true}}, '; Zone:', 'ProxyZone', '; Task:', 'Promise.then', '; Value:', HttpErrorResponse{headers: HttpHeaders{normalizedNames: Map{}, lazyUpdate: null, headers: Map{}}, status: 0, statusText: 'Unknown Error', url: 'http://192.168.5.128:3002/api/user/login', ok: false, name: 'HttpErrorResponse', message: 'Http failure response for http://192.168.5.128:3002/api/user/login: 0 Unknown Error', error: ProgressEvent{isTrusted: true}}, undefined – Techdive Jul 31 '19 at 04:36
  • @Techdive Hey, can u please put an `UPDATE` section in your question with whatever you have implemented as my suggestion alone with `error` messages . that would help me understand it better – Shashank Vivek Jul 31 '19 at 04:48
  • @ Shashank ... I implemented in my question – Techdive Jul 31 '19 at 05:11
  • @Techdive : Can u confirm that ypu are using ` {provide: Login2Service, useClass: Login2ServiceStub },` in `TestBed` code ? – Shashank Vivek Jul 31 '19 at 08:15
  • Adding Stub in the end is important is it ? I have defined as - {provide : LoginService , useClass: LoginService } ...the remaining declarations are same as you mentioned – Techdive Jul 31 '19 at 09:32
  • @Techdive Not `{provide : LoginService , useClass: LoginService } ` but `{provide : LoginService , useClass: LoginServiceStub} ` .Note I have put `LoginServiceStub` . This will replace your actual service call where it is making `http` call. This is the reason you are getting `http` error`, – Shashank Vivek Jul 31 '19 at 11:56
  • can you look into this issue - https://stackoverflow.com/questions/57673803/this-productdataservice-getallproducts-is-not-a-function-using-jasmine-karma – Techdive Aug 28 '19 at 04:42
  • @Techdive : sure, but i hope you'll select my answer (or provide me some update on it, if it wont work). Its a mutual practice on SO, I hope you for my point. putting effort on something with no response is demotivating. Let me take a look at your link – Shashank Vivek Aug 28 '19 at 10:36
  • Sure Shashank. I will appreciate your efforts! thanks in advance – Techdive Aug 28 '19 at 10:40
  • Hi SHashank. Thanks or your answer. Can you also please check this link - https://stackoverflow.com/questions/57690695/expected-response-with-status-null-null-for-url-null-to-equal-project11 – Techdive Aug 28 '19 at 11:15