I want to write unit tests for a component. The very first test fails, with this error:
Error: Expected undefined to be truthy.
it block outputting the error:
it('should create', () => {
    expect(component).toBeTruthy();
  });
login template:
<h3>Login</h3>
<form class="form form-group" (ngSubmit)="onSubmit()">
  <div class="row">
    <label for="email" class="login-form-label col-4">Email:</label>
    <input ngModel [(ngModel)]="email" name="email" (ngModelChange)="validateEmail()" type="email" id="email" class="col-3 form-control">
    <span class="error col-sm-4">{{ this.emailErr }}</span>
  </div>
  <br>
  <div class="row">
    <label for="password" class="login-form-label col-4">Wachtwoord:</label>
    <input ngModel [(ngModel)]="password" name="password" (ngModelChange)="validatePassword()" type="password" id="password" class="col-3 form-control">
    <span class="error col-sm-4">{{ this.passwordErr }}</span>
  </div>
  <input type="submit" [disabled]="!isValid()" value="Login" class="login-button col-1">
</form>
I tried:
describe('LoginComponent', () => {
  let component: LoginComponent;
  let fixture: ComponentFixture<LoginComponent>;
  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [ LoginComponent ],
      providers: [
        LoginComponent,
        { provide: RoutingService, useValue: MockRoutingService },
        { provide: AuthenticationService, useValue: MockAuthenticationService }
      ]
    })
    .compileComponents();
    fixture = TestBed.createComponent(LoginComponent);
    component = fixture.componentInstance;
  });
Also:
describe('LoginComponent', () => {
  let component: LoginComponent;
  let fixture: ComponentFixture<LoginComponent>;
  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [ LoginComponent ],
      providers: [
        LoginComponent,
        { provide: RoutingService, useValue: MockRoutingService },
        { provide: AuthenticationService, useValue: MockAuthenticationService }
      ]
    })
    .compileComponents();
  });
  beforeEach(() => {
    fixture = TestBed.createComponent(LoginComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });
And also:
describe('LoginComponent', () => {
  let component: LoginComponent;
  let fixture: ComponentFixture<LoginComponent>;
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ LoginComponent ],
      providers: [
        LoginComponent,
        { provide: RoutingService, useValue: MockRoutingService },
        { provide: AuthenticationService, useValue: MockAuthenticationService }
      ]
    })
    .compileComponents();
  }));
  beforeEach(() => {
    fixture = TestBed.createComponent(LoginComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });
LoginComponent:
@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
  private password = '';
  private email = '';
  private emailErr = '';
  private passwordErr = '';
  constructor(private authService: AuthenticationService, private routingService: RoutingService) { }
  ngOnInit() {
  }
  onSubmit() {
    this.emailErr = '';
    this.passwordErr = '';
    const response: { ['emailValid']: boolean, ['passwordValid']: boolean } = this.authService.login(this.email, this.password);
    const self = this;
    setTimeout(() => {
      if (response.emailValid === false) {
        self.emailErr = '* Your username was incorrect, please try again.';
        return;
      } else if (response.passwordValid === false) {
        self.passwordErr = '* Your password was incorrect, please try again.';
        return;
      }
      self.routingService.route('home');
    }, 300);
  }
  validateEmail() {
    this.emailErr = '';
    if (this.email === '') {
      this.emailErr = '* Please enter your email.';
    }
  }
  validatePassword() {
    this.passwordErr = '';
    if (this.password === '') {
      this.passwordErr = '* Please enter your password.';
    }
  }
  isValid() {
    if (this.password === '' || this.email === '') {
      return false;
    } else if (this.emailErr !== '' || this.passwordErr !== '') {
      return false;
    }
    return true;
  }
}
What am I missing here?
As nash11 pointed out, you definitely need to remove the LoginComponent from the providers list and the first beforeEach should run async. 
I would expect that doing this you actually should get a different message, telling you that ngModel is not a known property of an <input/>
There are two ways of getting your test to work, depending if you want the [{ngModel}] to work like it usually does or if you only want to shallow test your application where you're not interested in how [{ngModel}] actually works. 
So if you want that ngModel just works, you would need to import the FormsModule into your TestBed.
If you don't mind that ngModel works but is just a property which exists you could set the NO_ERRORS_SCHEMA inside your TestBed.
describe('AppComponent', () => {
    let component: AppComponent;
    let fixture: ComponentFixture<AppComponent>;
    beforeEach(async(() => {
        TestBed.configureTestingModule({
            declarations: [AppComponent],
            providers: [
              { provide: RoutingService, useValue: MockRoutingService },
              { provide: AuthenticationService, useValue: MockAuthenticationService }],
            // imports: [FormsModule] // import the FormsModule if you want ngModel to be working inside the test
            schemas: [NO_ERRORS_SCHEMA] // remove the FormsModule import and use that schema to only shallow test your component. Please refer to the official document for more information.
        })
            .compileComponents();
    }));
    beforeEach(() => {
        fixture = TestBed.createComponent(AppComponent);
        component = fixture.componentInstance;
    });
    it('should be created', () => {
      fixture.detectChanges();
      expect(component).toBeTruthy();
    });
});
Here is a working stackblitz
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With