13

I've a simple component which contains two input fields inside a form element. On clicking the submit button, it calls the addUser function on the component.

Component template is given below:

<div>
  <form [formGroup]="signupForm" (submit)="addUser($event)" role="form" class="form-horizontal">
      <label>Firstname:</label>
      <input type="text" formControlName="firstName"> 
      <label>Lastname:</label>
      <input type="text" formControlName="lastName">
      <input type="submit" id="btnSubmit" class="btn btn-primary btn-lg" value="Register" />
  </form>
</div>

Component definition is given below:

@Component({
  moduleId: module.id,  
  templateUrl: 'user.component.html'  
})
export class UserComponent {

  registered = false;

  constructor(
    private router: Router,
    private fb: FormBuilder,
    public authService: AuthService) {

      this.signupForm = this.fb.group({
            'firstName': ['', Validators.required],
            'lastName': ['', Validators.required]
        });        
  }

  addUser(event: any) {
      event.preventDefault();
      this.addUserInvoked = true;
      ......
      ......
      this.authService.register(this.signupForm.value)
        .subscribe(
        (res: Response) => {
            if (res.ok) {
                this.registered = true;
            }
        },
        (error: any) => {
            this.registered = false;                                
        });
  }
}

It works fine. However, in my unit test when I try to test that when click is invoked on submit button then the call to the addUser is made. But unfortunately the addUser function is not invoked.

Below is my sample unit test

class RouterStub {
  navigateByUrl(url: string) { return url; }
}


let comp: UserComponent;
let fixture: ComponentFixture<UserComponent>;

describe('UserComponent', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [ UserComponent ],
      schemas:      [NO_ERRORS_SCHEMA]
    });
  });

  compileAndCreate();
  tests();
});

function compileAndCreate() {
  beforeEach( async(() => {
    TestBed.configureTestingModule({
      providers: [        
        { provide: Router,      useClass: RouterStub },
        { provide: AuthService, useValue: authServiceStub },
        FormBuilder
      ]
    })
    .compileComponents().then(() => {
      fixture = TestBed.createComponent(UserComponent);
      comp = fixture.componentInstance;
    });
  }));
}

function tests() {
    it('should call addUser when submitted', () => { 
        const spy = spyOn(comp, 'addUser');  

        //*************below method doesn't work and it refreshes the page***************
        //let btnSubmit = fixture.debugElement.query(By.css('#btnSubmit'));
        //btnSubmit.nativeElement.click();

        let form = fixture.debugElement.query(By.css('form'));
        form.triggerEventHandler('submit', null);
        fixture.detectChanges();

        expect(comp.addUser).toHaveBeenCalled();
        expect(authServiceStub.register).toHaveBeenCalled();
        expect(comp.registered).toBeTruthy('user registered'); 
    });

}

I've tried

fixture.debugElement.query(By.css('#btnSubmit')).nativeElement.click()

and

fixture.debugElement.query(By.css('form')).triggerEventHandler('submit', null)

but I am still unable to invoke addUser function. I've seen a question already posted on SO here but it's not helpful either.

Community
  • 1
  • 1
A J Qarshi
  • 2,772
  • 6
  • 37
  • 53
  • Which step fails? `expect(comp.addUserInvoked).toBeTruthy();` is an inaccurate expectation, because if you aren't actually calling the method that never gets set. – jonrsharpe Nov 21 '16 at 15:00
  • This is a dummy expectation just to test that the on submit the function is invoked. – A J Qarshi Nov 21 '16 at 15:34
  • But is that the one that fails? Because the function is not invoked, *the spy is*, and you don't call through. Could you give a [mcve] with output? – jonrsharpe Nov 21 '16 at 15:35
  • In my actual application I have a service dependency and inside my submit handler function I invoke the service function. I want to make sure that the service function is invoked. I have already posted a question on SO which seems to be relevant. Please see it here http://stackoverflow.com/questions/40672106/angular2-unit-testing-observable-error-cannot-read-property-subscribe-of-un – A J Qarshi Nov 21 '16 at 15:37
  • Yes, I understand that. My point is that **the actual function is never invoked**. That's the whole point of spying it. So, for the last time, **which expectation fails?** Don't just say *"none of them worked"*, give useful, specific information. – jonrsharpe Nov 21 '16 at 15:42
  • I've updated the question. I started unit testing a week ago so I am sorry if I am unable to elaborate my problem. Or may be I am trying to write a wrong test. – A J Qarshi Nov 21 '16 at 16:00
  • I noticed it works well with Chrome 59. But it doesn't work with Chrome 60. – sasha_trn Jul 26 '17 at 10:22
  • try to call addUser by `component.addUser()` – Kamran Khatti Mar 29 '18 at 10:06

3 Answers3

4

Here is sample code: 1 : Replace Xcomponent with your component name 2 : Replace formID with id of your form.

import {async, ComponentFixture, TestBed} from '@angular/core/testing';

    import {FormsModule} from '@angular/forms';
    import {By} from '@angular/platform-browser';

    describe('Xcomponent', () => {
      let component: Xcomponent;
      let fixture: ComponentFixture<Xcomponent>;

      beforeEach(async(() => {
        TestBed.configureTestingModule({
          imports: [FormsModule],
          declarations: [Xcomponent]
        })
          .compileComponents();
      }));

      beforeEach(() => {
        fixture = TestBed.createComponent(Xcomponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
      });

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

      it('should call save() method on form submit', () => {
        /*Get button from html*/
        fixture.detectChanges();
        const compiled = fixture.debugElement.nativeElement;
        // Supply id of your form below formID
        const getForm = fixture.debugElement.query(By.css('#formID'));
        expect(getForm.triggerEventHandler('submit', compiled)).toBeUndefined();
      });

    });
XmenR
  • 47
  • 3
2
  1. You need to spyon the function you wanna check and the functions it depends on.
  2. Second call fixture.detectChanges after you dispatch the event.
  3. Also make sure your form is visible on the dom otherwise the query will return null

I could do it like this:

let yourService: YourService;
beforeEach(() => {
    fixture = TestBed.createComponent(YourComponent);
    component = fixture.componentInstance;
    store = TestBed.get(YourService);
    fixture.detectChanges();
});


it('should call the right funtion', () => {       
    spyOn(yourService, 'yourMethod');// or spyOn(component, 'yourMethod');       
    const fakeEvent = { preventDefault: () => console.log('preventDefault') };
    fixture.debugElement.query(By.css('form')).triggerEventHandler('submit', fakeEvent);
    expect(yourService.yourMethod).toHaveBeenCalledWith(
      //your logic here
    );
});
Eduardo Vargas
  • 8,752
  • 2
  • 20
  • 27
1

I had the same problem and the solution for me was that I had to import the 'FormsModule' into the configuration of my testing module.

TestBed.configureTestingModule({
            imports: [FormsModule]
});

Maybe this can help ?

djangofan
  • 28,471
  • 61
  • 196
  • 289
Ran_088
  • 27
  • 3