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處聲明並將其設置爲單例。在測試中覆蓋@Component.providers

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

Alexus,

您是否嘗試導入HTTP模塊到您的測試組件,並將其添加到 「供應商」 陣列?我認爲在這種情況下你必須指定所有的依賴關係。我假設您的LoginService需要{Http}作爲規定,但您的測試組件不會註冊{Http},因此無法找到要使用的實例。

編輯:

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

DOUBLE編輯!:

此外,你可能會想模擬出HTTP模塊,你會不會真的想你的單位期間發送請求測試。來自@ angular/http/testing的「MockBackend」足以滿足這種需求 - 在這種情況下,您會希望使用與Login Service一起使用的「provide」語法來提供一個使用MockBackend生成響應的Http模塊。