【问题标题】:How to mock service?如何模拟服务?
【发布时间】:2016-12-17 17:18:30
【问题描述】:

我的 LoginComponent 中有登录功能:

login() {
        this.loading = true;

        this.subscription = this.authenticationService.login(this.model.username, this.model.password)
            .subscribe(result => {
                this.em.changeNav(1);
                this.loading = false;
                this.Auth.setToken(result);
                this.router.navigate(['/code']);
                this.subscription.unsubscribe();
            },
            err => {
                this.error = JSON.parse(err._body).error;
                this.loading = false;
            });


    }

this.authenticationService.login是向api发送http请求的服务...

这是测试:

it('should login', fakeAsync(() => {
        spyOn(component, 'login');

        let button = fixture.debugElement.nativeElement.querySelector('button');
        button.click();

        //CHECK IF LOGIN FUNCTION CALLED
        fixture.whenStable().then(() => {
            expect(component.login).toHaveBeenCalled();
        })

    }));

如何模拟this.authenticationService.login 服务并在订阅方法中断言内容?

编辑

测试:

import { async, ComponentFixture, TestBed, fakeAsync, tick, inject } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';

import {Router} from '@angular/router';
import { Http, Request, RequestOptionsArgs, Response, XHRBackend, RequestOptions, ConnectionBackend, Headers, HttpModule, BaseRequestOptions } from '@angular/http';
import {LoginService} from './login.service';
import {
    MockBackend,
    MockConnection
} from '@angular/http/testing';
import {EmitterService} from '../emitter.service';
import {AuthTokenService} from '../auth-token.service';

import { LoginComponent } from './login.component';
import {Observable} from 'rxjs';

describe('LoginComponent', () => {
    let backend: MockBackend;
    let service: LoginService;

    let component: LoginComponent;
    let fixture: ComponentFixture<LoginComponent>;

    beforeEach(async(() => {
        class LoginServiceStub {
            login() { }
        };

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


        TestBed.configureTestingModule({
            declarations: [LoginComponent],
            imports: [
                FormsModule,
                HttpModule,
                ReactiveFormsModule,
                RouterTestingModule
            ],
            providers: [
                LoginService,
                EmitterService,
                AuthTokenService,
                { provide: LoginService, useClass: LoginServiceStub },
                //                { provide: Router, useClass: RouterStub }
            ]
        })
            .compileComponents();
    }));

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

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

    it('Should log in and navigate to dashboard', fakeAsync(inject([LoginService, Router], (authService: LoginService, router: Router) => {
        spyOn(component, 'login');
        let button = fixture.debugElement.nativeElement.querySelector('button');

        spyOn(authService, 'login').and.returnValue(Observable.of(true));
        button.click();
        tick();

        expect(component.login).toHaveBeenCalled();
        expect(component.loading).toBe(false);
    })));

});

问题是组件中的login 函数永远不会被调用当我在组件中的login 方法中进行console.log 时,它会显示消息...

这是 Html 部分:

<form name="form" class="form-horizontal" (ngSubmit)="f.form.valid && login()" #f="ngForm" novalidate>
<img class="loading-img" *ngIf="loading" src="data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==" />

              <div class="form-group" [ngClass]="{ 'has-error': f.submitted && !username.valid }">
             <label for="username" class="cols-sm-2 control-label">Email</label>
                <div class="cols-sm-10">
                    <div class="input-group">
                        <span class="input-group-addon"><i class="fa fa-user fa" aria-hidden="true"></i></span>
                        <input type="text" class="form-control" name="username" placeholder="Your email" [(ngModel)]="model.username" #username="ngModel" required />
                    </div>
                </div>
                <div *ngIf="f.submitted && !username.valid" class="help-block">Email is required</div>
            </div>

            <div class="form-group" [ngClass]="{ 'has-error': f.submitted && !password.valid }">
                 <label for="password" class="cols-sm-2 control-label">Password</label>
                <div class="cols-sm-10">
                    <div class="input-group">
                        <span class="input-group-addon"><i class="fa fa-lock fa-lg" aria-hidden="true"></i></span>
                        <input type="password" placeholder="Your password"  class="form-control" name="password" [(ngModel)]="model.password" #password="ngModel" required />
                    </div>
                </div>
                <div *ngIf="f.submitted && !password.valid" class="help-block">Password is required</div>
            </div>

            <div class="form-group">
                <button id="login" type="submit"  class="btn btn-primary">Login</button>


            <div *ngIf="error" style="margin-top: 20px;" class="text-center alert alert-danger">{{error}}</div>
            </div>

            <div class="form-group text-center login-down" >
                <a routerLink="/register" routerLinkActive="active">Register now</a>
                <a routerLink="/forgot" routerLinkActive="active">Forgot password</a>

            </div>
        </form>

【问题讨论】:

    标签: unit-testing angular angular2-http


    【解决方案1】:

    您可以为服务创建模拟类:

    class AuthenticationServiceStub {
        login() {}
    };
    

    然后在configureTestingModule提供它:

    TestBed.configureTestingModule({
      declarations: [TestComponent],
      providers: [
        { provide: AuthenticationService, useClass: AuthenticationServiceStub },
        { provide: Router, useClass: RouterStub }
      ]
    })
    

    在你的测试中注入

    inject([AuthenticationService, Router], 
       (authService: AuthenticationService, router: Router) =>
    

    将其包裹在 async(+whenStable) 或 fakeAsync(+tick) 或直接使用 jasmine.done 等待异步方法的执行

    it('Should log...', fakeAsync(inject([AuthenticationService, Router]
    

    并模拟 login 方法,例如:

    spyOn(authService, 'login').and.returnValue(Observable.of(true) );
    

    Plunker Example

    这是整个规范:

    describe('Welcome component tests', () => {
        let comp: TestComponent;
        let fixture: ComponentFixture<TestComponent>;
        let de: DebugElement;
        let el: HTMLElement;
    
        beforeEach(async(() => {
            class AuthenticationServiceStub {
                login() {}
            };
    
            class RouterStub {
                navigateByUrl(url: string) { return url; }
            }
    
            TestBed.configureTestingModule({
                declarations: [TestComponent],
                providers: [
                    { provide: AuthenticationService, useClass: AuthenticationServiceStub },
                    { provide: Router, useClass: RouterStub }
                ]
            })
            .compileComponents()
        }));
    
        beforeEach(() => {
            fixture = TestBed.createComponent(TestComponent);
            comp = fixture.componentInstance;
    
            de = fixture.debugElement.query(By.css('.login'));
            el = de.nativeElement;
            fixture.detectChanges();
        });
    
        it('Should log in and navigate to dashboard', fakeAsync(inject([AuthenticationService, Router], (authService: AuthenticationService, router: Router) => {
            const spy = spyOn(router, 'navigateByUrl');
            spyOn(authService, 'login').and.returnValue(Observable.of(true) );
    
            el.click();
            tick();
            const navArgs = spy.calls.first().args[0];
    
            expect(navArgs).toBe('/dashboard');
        })));
    });
    

    【讨论】:

    • 我有问题让它工作......我收到这个错误:inline template:38:16 caused by: router.routerState is undefined
    • `我发现这部分是因为它:{ provide: Router, useClass: RouterStub }
    • 你能在 plunker 中重现它吗?我模拟 router.navigateByUrl
    • 但是你使用了导航方法
    • 这可能是我使用的角度版本的问题2.1.0 我试图排除路由并在成功请求中添加true 变量,但测试仍然无法正常工作......
    猜你喜欢
    • 2015-02-10
    • 1970-01-01
    • 2015-09-19
    • 1970-01-01
    • 1970-01-01
    • 2023-03-03
    • 2018-02-19
    • 1970-01-01
    相关资源
    最近更新 更多