Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to stub imported module without methods in Angular?

I have an Angular Application with Jasmine Testing Framework. The Application has a Service called AuthService that handles decoding JSON Web Tokens:

auth.service.ts

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

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

Now I would like to stub the module jwtDecode and return a fake value for the purpose if testing:

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');
});

Because 'fake-token' is not a valid JSON Web Token, my tests fail with the message:

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

This is probably an error generated from the jwt-decode module, which is expected. I do not want to have to include another module just to create valid JSON Web Tokens for testing purposes. Instead, I'd like to stub the functionality of jwtDecode().

What I've tried

1. Using spyOn on jwtDecode

When I use spyOn, I need an object with a method. So for the imported jwtDecode this won't work, since it is a function itself:

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

2. Using callFake on getTokenPayload

I've tried using:

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

...and that prevents any errors from happening. However, my code coverage report now shows that the function getTokenPayload is not covered. Moreover, I have other function in the application that use external NPM Modules and I don't want to ignore code coverage since they might have other implementations inside the method that should be tested.

3. Using createSpy on jwtDecode

I tried overriding the imported jwtDecode and create a spy:

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

This gives me the same error as above, indicating that jwtDecode is not overridden inside my actual AuthService Service.

4. Using the window as the Object

From this question I read that global modules might be attached to the window Object. Hence, I tried doing the same thing for jwt-decode:

inside the test...

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

Unfortunately, both values are undefined on the window Object.

Question

I guess in general the question becomes:

How to stub imported NPM modules? Especially, how to stub them if they are not an object, but a function (without a method to use in Jasmine spyOn)?

like image 468
Nicky Avatar asked Jan 27 '18 17:01

Nicky


1 Answers

You're very close! Since a service is just a class, the best way to test it is to instantiate a new one and spy on it, as it appears you are doing. If you want to spy on the imported method, you will need to somehow include it in your service. Otherwise, there is no way for your test to know what that method is.

So have a property on your service:

jwtDecode = jwtDecode; // the imported one

And called it as this.jwtDecode in your getTokenPayload method.

Then the following will work:

const service = new AuthService( /* constructor args */ );

const jwtDecode = spyOn(service, 'jwtDecode');
jwtDecode.and.returnValue('fake-token');
like image 145
vince Avatar answered Nov 05 '22 05:11

vince