【问题标题】:How to stub imported module without methods in Angular?如何在 Angular 中不使用方法存根导入的模块?
【发布时间】:2018-07-06 19:35:27
【问题描述】:

我有一个带有 Jasmine 测试框架的 Angular 应用程序。该应用程序有一个名为 AuthService 的服务,用于处理 JSON Web 令牌的解码:

auth.service.ts

import * as jwtDecode from 'jwt-decode';
...

@Injectable()
export class AuthService {
  ...
  public getTokenPayload(token) {
    return jwtDecode(token);
  }
}

现在我想存根模块 jwtDecode 并在测试时返回一个假值:

auth.service.spec.ts

...

it('should get a token payload', () => {
  const fakeToken = 'fake-token';

  spyOn(service, 'getTokenPayload').and.callThrough();
  const tokenPayload = service.getTokenPayload(fakeToken);

  expect(tokenPayload).toBe('fake-token');
});

因为 'fake-token' 不是有效的 JSON Web 令牌,我的测试失败并显示以下消息:

InvalidTokenError: Invalid token specified: undefined is not an object (evaluating 'str.replace')

这可能是jwt-decode 模块产生的错误,这是预期的。我不想仅仅为了测试目的而包含另一个模块来创建有效的 JSON Web 令牌。相反,我想存根 jwtDecode() 的功能。

我尝试过的

1。在jwtDecode 上使用spyOn

当我使用spyOn 时,我需要一个带有方法的对象。所以对于导入的jwtDecode 这将不起作用,因为它本身就是一个函数:

spyOn(jwtDecode, '<method?>').and.callFake(() => 'fake-token');

2。在getTokenPayload 上使用callFake

我尝试过使用:

spyOn(service, 'getTokenPayload').and.callFake(() => 'fake-token');

...这样可以防止发生任何错误。但是,我的代码覆盖率报告现在显示函数 getTokenPayload 没有被覆盖。此外,我在应用程序中还有其他使用外部 NPM 模块的功能,我不想忽略代码覆盖率,因为它们可能在应该测试的方法内有其他实现。

3。在jwtDecode 上使用createSpy

我尝试覆盖导入的jwtDecode 并创建一个间谍:

const jwtDecode = jasmine.createSpy('jwtDecode').and.returnValue('fake-token');

这给了我与上面相同的错误,表明jwtDecode 没有在我的实际AuthService 服务中被覆盖。

4。使用window 作为对象

this question 我读到全局模块可能附加到window 对象。因此,我尝试为jwt-decode 做同样的事情:

测试内...

console.log(window.jwtDecode); // undefined
console.log(window.jwt_decode); // undefined

不幸的是,window 对象上的两个值都是 undefined

问题

我想一般来说问题变成了:

如何存根导入的 NPM 模块?特别是,如果它们不是对象而是函数(没有在 Jasmine spyOn 中使用的方法),如何存根它们?

【问题讨论】:

  • 我认为你可以监视 window.jwtDecode。看看stackoverflow.com/questions/9510148/…
  • 感谢您的回答。不幸的是,我早些时候遇到了这种方法,但没有运气。我已经更新了这个问题,提供了我在这种方法中的发现。

标签: angular unit-testing typescript jasmine karma-jasmine


【解决方案1】:

只需监视jwt-decode 模块中的default 导出,即jwtDecode 函数。这样可以避免按照建议污染您的服务。

例如,在您的规范文件中:

import * as jwt from 'jwt-decode';
...
// return whatever data structure you require
spyOn(jwt, 'default').and.returnValue({ someData: ['x', 'y', 'z'] });

【讨论】:

    【解决方案2】:

    你非常接近!由于服务只是一个类,因此测试它的最佳方法是实例化一个新服务并监视它,就像您正在做的那样。 如果您想监视导入的方法,则需要以某种方式将其包含在您的服务中。否则,您的测试无法知道该方法是什么。

    所以在你的服务上有一个属性:

    jwtDecode = jwtDecode; // the imported one
    

    并在您的 getTokenPayload 方法中将其称为 this.jwtDecode

    然后以下将起作用:

    const service = new AuthService( /* constructor args */ );
    
    const jwtDecode = spyOn(service, 'jwtDecode');
    jwtDecode.and.returnValue('fake-token');
    

    【讨论】:

    • 感谢您的回复!我已经尝试过这个解决方案,但我认为语法不正确(它也不起作用)。 createSpy 的第二个参数应该是一个函数(根据文档)。另外,第一个参数'service' 是否指代变量名之类的东西?我确实尝试过const mockService = jasmine.createSpy('service', jwtDecode).and.returnValue('fake-token');const mockService = jasmine.createSpy('AuthService', jwtDecode).and.returnValue('fake-token');,但没有任何运气。有什么想法吗?
    • 糟糕——我的意思是.createSpyObj——现在更新我的答案
    • 我不确定...我现在正在审查您的问题并思考。
    • createSpyObj 语法现在是正确的,但mockService 值只是Object{jwtDecode: function () { ... }},所以我无法在该对象上测试getTokenPayload。我也尝试将'service''AuthService' 作为createSpyObj 的第一个参数,但没有运气。 mockService 仍然是 Object{jwtDecode: function () { ... }}
    • 嗯..我的错误。我的回答是,如果您使用 TestBed 设置组件并将您的服务添加为提供者。我刚刚更新了测试服务的答案。如果这不起作用,请告诉我,如果不起作用,我可能需要查看更多代码。
    猜你喜欢
    • 1970-01-01
    • 2019-04-08
    • 2020-07-23
    • 1970-01-01
    • 2020-05-16
    • 1970-01-01
    • 1970-01-01
    • 2018-03-28
    相关资源
    最近更新 更多