【问题标题】:Angular: Is there a way to mock the value of PLATFORM_ID in a unit test?Angular:有没有办法在单元测试中模拟 PLATFORM_ID 的值?
【发布时间】:2019-02-10 15:26:00
【问题描述】:

我正在使用 Angular Universal。根据我是在服务器还是浏览器平台上运行,我有一个行为不同的路由的守卫。这是警卫:

export class UniversalShellGuard implements CanActivate {
  private isBrowser: boolean;

  constructor(@Inject(PLATFORM_ID) private platformId: Object) {
    console.log('PLATFORM_ID = ' + platformId);
    this.isBrowser = isPlatformBrowser(this.platformId);
  }

  canActivate(): Observable<boolean> | Promise<boolean> | boolean {
    return !this.isBrowser;
  }
}

如您所见,守卫正在注入PLATFORM_ID,并使用它来确定他是否canActivate()

现在,我想为守卫编写一个简单的单元测试并执行以下操作:

describe('UniversalShellGuard', () => {
  let guard: UniversalShellGuard;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        UniversalShellGuard,
        // Idea: for now I just want to test the behaviour if I would be on the browser, so I would just use a fixed value for PLATFORM_ID
        { provide: PLATFORM_ID, useValue: PLATFORM_BROWSER_ID },
      ],
    });

    guard = TestBed.get(UniversalShellGuard);
  });

  it('should deny', () => {
    expect(guard.canActivate()).toBe(false);
  });
});

但它给出了以下错误:

ERROR in ./src/app/universal-shell.guard.spec.ts
Module not found: Error: Can't resolve '@angular/common/src/platform_id' in '/my-app-path/src/app'
 @ ./src/app/universal-shell.guard.spec.ts 4:0-70 11:50-69
 @ ./src sync \.spec\.ts$
 @ ./src/test.ts

我什至尝试了一个简单直接的防护结构,不使用角度TestBed

it('should deny', () => {
  const guard = new UniversalShellGuard(PLATFORM_BROWSER_ID);
  expect(guard.canActivate()).toBe(false);
});   

同样的错误。

有没有办法为PLATFORM_ID 提供一个固定值,以便正确地对这样的守卫进行单元测试?

【问题讨论】:

    标签: angular unit-testing guard angular-universal


    【解决方案1】:

    进行测试的另一种方法是模拟isPlatformBrowser 的结果。为此,您应该创建该函数的包装器:

    export class UniversalShellGuard implements CanActivate {
        ...
        private isBrowser(): boolean {
            return isPlatformBrowser(this.platformId);
        }
    }
    

    然后,使用jasmine Spy,您可以模拟isBrowser() 方法的返回值:

    describe('UniversalShellGuard', () => {
        let guard: UniversalShellGuard;
    
        beforeEach(() => {
          TestBed.configureTestingModule({
            providers: [
              UniversalShellGuard,
              { provide: PLATFORM_ID, useValue: '' },
            ],
          });
    
          guard = TestBed.get(UniversalShellGuard);
        });
    
        it('should deny', () => {
          spyOn(guard, 'isBrowser').and.returnValue(true);
          expect(guard.canActivate()).toBe(false);
        });
    });
    

    【讨论】:

    • isBrowser 是一个私有方法,你会在 TS 代码中得到一个错误,因为你不能访问私有方法并监视它们
    【解决方案2】:

    PLATFORM_BROWSER_ID 不是公共 API 的一部分,这就是它从深层路径导入它的原因,这是不允许的。相反,您可以输入'browser':

    { provide: PLATFORM_ID, useValue: 'browser' },
    

    对于任何其他平台,您可以使用these 值:

    export const PLATFORM_BROWSER_ID = 'browser';
    export const PLATFORM_SERVER_ID = 'server';
    export const PLATFORM_WORKER_APP_ID = 'browserWorkerApp';
    export const PLATFORM_WORKER_UI_ID = 'browserWorkerUi';
    

    【讨论】:

    • 这很愚蠢,但它确实有效...isPlatformBrowser 方法应该接收一个对象,但用字符串模拟就可以了。
    • 不错。我在警卫单元测试中也遇到了这个问题,最后将我的configureTestingModule 代码包装到一个configure() 函数中,该函数将平台ID 作为参数并在provide 中使用它。 configure() 函数由 beforeEach 代码在具有 SSR 测试的 describe 块和具有正常浏览器测试的另一个 describe 块中调用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-10-01
    • 2021-08-23
    • 2017-08-03
    • 1970-01-01
    • 2020-11-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多