Angular2 unit testing a component that uses MdlSna

2019-09-19 07:01发布

问题:

I've got the following component:

import { Component, OnInit } from '@angular/core';
import {FormBuilder, FormGroup, FormControl, Validators} from "@angular/forms";
import {ValidationService} from "../../services/validation.service";
import {Router} from "@angular/router";
import {UsersService} from "../../services/users.service";
import {MdlSnackbarService} from "angular2-mdl";

@Component({
  selector: 'app-signup',
  templateUrl: 'signup.component.html',
  styleUrls: ['signup.component.css'],
  providers: [UsersService]
})
export class SignupComponent implements OnInit {

  form: FormGroup;

  constructor(private fb: FormBuilder,
              private router: Router,
              private usersService: UsersService,
              private mdlSnackbarService: MdlSnackbarService) {
    this.form = fb.group({
      "email": new FormControl("", [Validators.required, ValidationService.emailValidator]),
      "password": new FormControl("", Validators.required)
    });
  }

  ngOnInit() {
  }

  onSignup() {
    if (this.form.valid) {
      let email = this.form.value.email;
      let password = this.form.value.password;
      this.usersService.signup(email, password)
        .then(() => {
          this.router.navigate(['/app/home']);
        })
        .catch(err => {
          this.mdlSnackbarService.showToast(err);
        });
    }

  }

}

And I am trying to setup some unit tests for this, but after quite a few hours I am still unable to run the simplest test (the one autogenerated by the angular CLI):

fdescribe('SignupComponent', () => {
  let component: SignupComponent;
  let fixture: ComponentFixture<SignupComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ SignupComponent ],
      providers: [UsersService, AngularFire, MdlDialogOutletService],
      imports: [
        AngularFireModule.initializeApp(firebaseConfig),
        ReactiveFormsModule,
        CommonModule,
        RouterTestingModule.withRoutes([
          // { path: 'settings/:collection/edit/:item', component: DummyComponent }
        ])
      ]
    })
    .compileComponents();
  }));

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

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

I get the following error: No provider for MdlSnackbarService! so what I did is adding MdlSnackbarService to the providers configureTestingModule:

providers: [UsersService, AngularFire, MdlDialogOutletService, MdlSnackbarService]

but then, the error I've got is Error: No component factory found for MdlSnackbarComponent. Did you add it to @NgModule.entryComponents? which I have no clue how to fix. I haven't found any answer related with unit testing.

Does anyone knows how to fix this?

回答1:

You should be using mocks for all these services, i.e. the Router, the UserService, and the MdlSnackbarService. You want to be able to control what these services do during the tests. You don't really care what the services do per se. What matters is how the component interacts with them. This is what you want to test.

To set up the mock you can do something like

let router;
let userService;
let snackbar;

beforeEach(() => {
   router = { navigate: jasmine.createSpy('navigate') };
   snackbar = { showToast: jasmine.createSpy('showToast') };
   userService = { signup: (email, pass) => null };

   TestBed.configureTestingModule({
     providers: [
       { provide: Router, useValue: router },
       { provide: UserService, useValue: userService },
       { provide: MdlSnackbarService, useValue: snackbar }
     ]
   });;
});

Now in your tests, you can control what the UserService does, i.e. return a successful promise, or return an error promise. This way you can can test how the component reacts to both situations.

it('should navigate on success', async(() => {
  spyOn(userService, 'signup').and.returnValue(Promise.resolve());

  component.signup();

  fixture.whenStable().then(() => {
    expect(router.navigate).toHaveBeenCalledWith(['/app/home']);
  })
}))

it('should show toast on error', async(() => {
  spyOn(userService, 'signup').and.returnValue(Promise.reject('error'));

  component.signup();

  fixture.whenStable().then(() => {
    expect(snackbar.showToast).toHaveBeenCalledWith('error');
  })
}))

That's basically how you want to test the component and how it interacts with these services.

As far as the error Error: No component factory found for MdlSnackbarComponent., you need to add that component in the test bed declarations just like you would any other component you need. If you don't want to do this, just mock the component by making a dummy component using the same selector, and adding that component to the declarations.