4

После прочтения этого guide Я решил проверить мою простую страницу входа, содержащую только 2 поля ввода и кнопку отправки. Затем компонент использует LoginService для передачи этих данных в бэкэнд.Не удалось проверить компонент с услугой

(отметить также, что я новичок в модульное тестирование, как таковой, так что я не уверен, что это хороший подход, поскольку, как проверить такой компонент.)

Для начала, я хотел только для проверки, если начальное значение #username входной элемент пуст. Но я не мог даже сделать спецификации для работы в связи с ниже известными проблемами:

Chrome 55.0.2883 (Windows 7 0.0.0) LoginComponent Username field should be empty FAILED 
     Failed: Unexpected value 'Http' imported by the module 'DynamicTestModule' 
     Error: Unexpected value 'Http' imported by the module 'DynamicTestModule' 
     TypeError: Cannot read property 'detectChanges' of undefined 
Chrome 55.0.2883 (Windows 7 0.0.0): Executed 4 of 4 (1 FAILED) (0 secs/0.348 secs) 

Когда я попытался удалить модуль HTTP, я получил эту ошибку:

Chrome 55.0.2883 (Windows 7 0.0.0) LoginComponent Username field should be empty FAILED 
     Error: DI Error 
     Error: Uncaught (in promise): Error: No provider for Http! 
     TypeError: Cannot read property 'detectChanges' of undefined 
Chrome 55.0.2883 (Windows 7 0.0.0): Executed 4 of 4 (1 FAILED) (0 secs/0.456 secs) 

login.component. HTML

<div class="login jumbotron center-block"> 
    <h1>Login</h1> 

    <form (ngSubmit)="onSubmit($event)" #loginForm="ngForm"> 

    <div class="form-group"> 
     <label for="username">Username</label> 
     <input type="text" class="form-control" [(ngModel)]="model.username" name="username" 
       placeholder="Username" #username="ngModel" required> 
     <div [hidden]="username.valid || username.pristine" class="alert alert-danger"> Username is required </div> 
    </div> 
    <div class="form-group"> 
     <label for="password">Password</label> 
     <input type="password" class="form-control" [(ngModel)]="model.password" name="password" placeholder="Password" #password="ngModel" required> 
     <div [hidden]="password.valid || password.pristine" class="alert alert-danger"> Password is required </div> 
    </div> 

    <button type="submit" class="btn btn-default" [disabled]="!loginForm.form.valid" >Submit</button> 
    <a [routerLink]="['/signup']">Click here to Signup</a> 
    </form> 
</div> 

login.component.ts

import { Component }  from '@angular/core'; 
import { Router }   from '@angular/router'; 
import { LoginService } from '../services/login.service'; 
import { User }   from '../extensions/user.class'; 

@Component({ 
    moduleId: module.id, 
    selector: 'login', 
    templateUrl: '../templates/login.component.html', 
    styleUrls: [ '../styles/login.component.css' ], 
    providers: [ LoginService ] 
}) 
export class LoginComponent { 

    private submitted = false; 
    private model = new User(); 

    constructor(
    private router: Router, 
    private loginService: LoginService 
) {} 

    public onSubmit(event: any): void { 
    event.preventDefault(); 
    if (! this.submitted) { 
     this.submitted = true; 

     if (this.model.username && this.model.password) { 
     this.loginService.login(this.model).then((token) => { 
      localStorage.setItem('id_token', token.id); 
      this.router.navigate(['home']); 
     }).catch((error) => this.onLoginFailed(error)); 
     } else { 
     console.warn('No username or password provided'); 
     } 

    } 
    } 

    private onLoginFailed(error: any): void { 
    //// errors are already handled in login-service //// 
    console.error(error); 
    this.submitted = false; /// reset form submit funcitonality /// 
    } 

    public signup(event: any): void { 
    event.preventDefault(); 
    this.router.navigate(['signup']); 
    } 
} 

login.component.spec.ts

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

import { FormsModule }      from '@angular/forms'; 
import { RouterTestingModule }    from '@angular/router/testing'; 
import { Component }       from '@angular/core'; 
import { Location }       from '@angular/common'; 

import { LoginComponent }     from './login.component'; 
import { LoginService }      from '../services/login.service'; 
import { Http } from '@angular/http'; 

import { User }   from '../extensions/user.class'; 

@Component({ 
    template: '' 
}) 
class DummyComponent{} 

class LoginServiceStub { 
    login(user: User){ 
    return true; 
    } 
} 

describe('LoginComponent',() => { 
    let comp:  LoginComponent; 
    let fixture: ComponentFixture<LoginComponent>; 
    let de:  DebugElement; 
    let el:  HTMLElement; 
    let location: Location; 

    // async beforeEach 
    beforeEach(async(() => { 

    TestBed.configureTestingModule({ 
     declarations: [ LoginComponent, DummyComponent ], // declare the test component 
     providers: [ 
     { provide: LoginService, useClass: LoginServiceStub } 
     ], 
     imports: [ 
     FormsModule , 
     RouterTestingModule.withRoutes([ 
     { path: 'singup', component: DummyComponent } 
     ]) 
     ] 
    }).compileComponents() // compile template and css 
    .then(() => { 
     fixture = TestBed.createComponent(LoginComponent); 
     comp = fixture.componentInstance; // LoginComponent test instance 
     de = fixture.debugElement.query(By.css('input[name="username"]')); 
     el = de.nativeElement; 
    }); 

    })); 

    it('Username field should be empty',() => { 
    fixture.detectChanges(); 
    expect(el.textContent).toContain(''); 
    }); 

}); 

ответ

2

Проблема заключается в том, что LoginService объявлен в компонент уровня

@Component({ 
    providers: [ LoginService ] 
}) 

Это вытеснит любую ту же услугу объявленную на модуль уровня, который где вы объявляете издеваются в тесте. Есть несколько вещей, которые вы можете сделать:

  1. Не объявляйте сервис на уровне компонентов. Если нет веской причины охватить его компонентом, просто объявите его на @NgModule.providers и сделайте его одиночным.

  2. Переверните @Component.providers в тесте.

    TestBed.configureTestingModule({}) 
    TestBed.overrideComponent(LoginComponent, { 
        set: { 
        providers: [ 
         { provide: LoginService, useClass: LoginServiceStub } 
        ] 
        } 
    }); 
    
0

Alexus,

Вы пытались импортировать модуль Http в тестовом компоненте и добавить его в массив "поставщиков"? Я думаю, вам нужно будет указать все ваши зависимости в этом случае. Я предполагаю, что ваш LoginService требует {Http} как условие, но ваш тестовый компонент не регистрирует {Http}, поэтому он не может найти экземпляр для использования.

EDIT:

TestBed.configureTestingModule({ 
    declarations: [ LoginComponent, DummyComponent ], // declare the test component 
    providers: [ 
    { provide: LoginService, useClass: LoginServiceStub }, 
    Http, 
    ], 
    imports: [ 
    FormsModule , 
    RouterTestingModule.withRoutes([ 
    { path: 'singup', component: DummyComponent } 
    ]) 
    ] 

DOUBLE EDIT !:

Кроме того, вы, вероятно, хотите, чтобы дразнить из модуля HTTP, так как вы не будете на самом деле хотите, чтобы послать запрос во время вашего устройства контрольная работа. «MockBackend» из @ angular/http/testing достаточно для этого - в этом случае вы хотели бы использовать синтаксис «обеспечить», который вы используете с помощью службы входа, чтобы предоставить модуль Http, который использует MockBackend для генерации ответов.