【问题标题】:Angular 4: test if window.location.href has been calledAngular 4:测试是否已调用 window.location.href
【发布时间】:2017-07-03 08:23:24
【问题描述】:

我有一个AuthGuard 服务负责检测用户是否已登录。如果未登录,我会将用户重定向到我们的 oauth 提供程序 url。

import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';

import { environment } from './../../environments/environment';
import { Session } from './../core/security/session.service';

@Injectable()
export class AuthGuard implements CanActivate {
  /**
   * Class constructor.
   * @constructor
   *
   * @param {Session} - Instance of session.
   */
  constructor(private session: Session) {}

  /**
   * Method to implements from CanActivate interface.
   * Check if a user is authenticated.
   *
   * @return {boolean}
   */
  canActivate(): boolean {
    if (this.session.isActive()) {
      return true;
    }

    this.redirectToProvider();
    return false;
  }

  /**
   * Redirect to Identity unauthorized url.
   */
  private redirectToProvider() {
    const unauthorizeUrl = environment.api.identity.unauthorizeUrl;
    window.location.href = unauthorizeUrl;
  }
}

我想知道当会话不存在时是否调用了window.location.href。这是我到目前为止所做的:

import { TestBed, async, inject } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';

import { AuthGuard } from './auth-guard.service';
import { Session } from './../core/security/session.service';

describe('AuthGuard', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        AuthGuard,
        Session
      ],
      imports: [RouterTestingModule]
    });
  });

  describe('.canActivate', () => {
    describe('active session', () => {
      it('returns true',
        async(inject([AuthGuard, Session], (guard, session) => {
          session.set({ name: 'user' });

          expect(guard.canActivate()).toBeTruthy();
        })
      ));
    });

    describe('no session', () => {
      it('redirects the user',
        async(inject([AuthGuard, Session], (guard, session) => {
          spyOn(window.location, 'href');
          session.destroy();

          expect(guard.canActivate()).toBeFalsy();
          expect(window.location.href).toHaveBeenCalled();
        })
      ));
    });
  })
});

但它给了我以下错误:

Failed: <spyOn> : href is not declared writable or has no setter

有没有办法模拟窗口对象来实现这一点,还是我需要依赖一些特殊的类来处理这种重定向,以便我可以在测试中注入它们?

【问题讨论】:

  • 为什么不使用 this.router.navigate 或 this.router.navigateByUrl 而不是 window.location.href
  • 我试过了,但this.router.navigateByUrl 似乎只适用于 Angular 注册的路由。但是,就我而言,我正在尝试重定向到外部 url。

标签: angular testing


【解决方案1】:

您可以注入window 作为注入令牌。 Angular 在@angular/common 中还有一个DOCUMENT DI 令牌,您可以直接与document.location.href 一起使用。

import { InjectionToken } from '@angular/core';

export const WindowToken = new InjectionToken('Window');
export function windowProvider() { return window; }

添加到app.module.ts:

providers: [
    ...
    { provide: WindowToken, useFactory: windowProvider }
  ]

并将其注入服务中:

constructor(@Inject(WindowToken) private window: Window, private session: Session)

在您的规范文件中,模拟窗口对象并对其进行测试。我创建了一个包含两个测试服务(一个依赖于另一个)的工作示例。该服务是使用 Angular 的静态注入器创建的:

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

import { CustomHrefService } from './custom-href.service';
import {AppModule} from '../app.module';
import {WindowToken} from './window';
import {Injector} from '@angular/core';
import {CustomHref2Service} from './custom-href-2.service';

const MockWindow = {
  location: {
    _href: '',
    set href(url: string) {
      this._href = url;
    },
    get href() {
      return this._href;
    }
  }
};

describe('CustomHrefService', () => {
  let service: CustomHrefService;
  let setHrefSpy: jasmine.Spy;

  beforeEach(() => {
    setHrefSpy = spyOnProperty(MockWindow.location, 'href', 'set');

    const injector = Injector.create({
      providers: [
        { provide: CustomHrefService, useClass: CustomHrefService, deps: [WindowToken, CustomHref2Service]},
        { provide: CustomHref2Service, useClass: CustomHref2Service, deps: []},
        { provide: WindowToken, useValue: MockWindow}
      ]
    });
    service = injector.get(CustomHrefService);
  });

  it('should be registered on the AppModule', () => {
    service = TestBed.configureTestingModule({ imports: [AppModule] }).get(CustomHrefService);
    expect(service).toEqual(jasmine.any(CustomHrefService));
  });

  describe('#jumpTo', () => {
    it('should modify window.location.href', () => {
      const url = 'http://www.google.com';
      service.jumpTo(url);
      expect(setHrefSpy).toHaveBeenCalledWith(url);
    });
  });
});

【讨论】:

  • 不需要编辑代码以满足测试。一段简单的代码就做了很多改动。
  • @CularBytes 上面的代码不是hack,它是一个很好的实践。建议避免直接引用全局对象,以防您的应用程序在其他环境(即 Angular Universal)中使用。与使用虚拟 DOM 的 Jest 不同,Karma 在浏览器中进行测试,并且无法直接存根或模拟窗口对象。无论如何,我不认为 OP 描述的情况是一个非常常见的测试用例,除非您的目标是 100% 覆盖 - 有办法避免测试。
  • 正是我需要的,我加了replace(url) {this.href = url; },假的也替换了
  • 如果只需要测试位置变化,我发现如果仍然需要真实的窗口或文档,创建 locationToken 不太可能产生副作用:indepth.dev/tree-shakable-dependencies-in-angular-projects
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多