35

Let's say i have an angular 6 component with a method test which returns some value:

import { doSomething } from './helper';

@Component({
    ...
})
export class AppComponent {
    test() {
        const data = doSomething(1);
        return data.something ? 1: 2;
    }
}

doSomething is just a simple helper function:

export function doSomething() {
    return { something: 1 };
}

Is it possible to mock or spy this function in a unit test (so i can control its returnValue)? Or do i have to change my approach in the component?

Please note: doSomething() can be a lodash function, a const, a class etc. I just tried to keep the example as simple as possible.


Things i've tried:

  • SpyOn doesn't work because function is not attached to anything

  • Importing an mock-function into the imports array of TestBed.configureTestingModule gives Unexpected value 'doSomething' imported by the module 'DynamicTestModule'. Please add a @NgModule annotation.

  • Creating a service for it works but it feels silly to have to create services for each imported function

Liam
  • 27,717
  • 28
  • 128
  • 190
user7995820
  • 412
  • 1
  • 4
  • 10
  • May I ask why you want to mock it? You also mentioned `SpyOn`, which implies spying instead of mocking. – a better oliver May 30 '18 at 13:03
  • I want to be able to control the returnValue of the function so i dont have to care about logics in this function since it should have a separate unit test. Maybe mocking is the wrong word. Spying would also be good as long as i can control the return value within the unit test – user7995820 May 30 '18 at 13:07
  • you can try spyOn(service, 'function').and.returnValue('your value here') – Fateh Mohamed May 30 '18 at 13:20
  • @FatehMohamed What would `service` be though? `doSomething` does not belong to an object or so – user7995820 May 30 '18 at 13:23
  • You are right: the function should have it's own test. But here's the thing: The function should always return the same output for a specified input and you should be able to control the input in your tests. If that's not the case then that could be a sign of a design issue. I assume you can instruct the build process to include a different file in your tests, but I rather encourage you to rethink the approach. – a better oliver May 30 '18 at 13:26
  • 1
    I agree, but wouldnt that actually make a stronger case for mocking/spying? I expect returnValue x when y is inserted, and A when B is inserted. What if this function requires 2 classes who both also require parameters. Seems a lot of work and error prone. – user7995820 May 30 '18 at 13:36
  • @abetteroliver How would you approach it differently though? :) – user7995820 May 30 '18 at 13:37
  • It really depends on your actual use case / code. Maybe you can show us a more realistic example. The basic idea of unit testing is that you don't care about the implementation (the internals) during testing. You call `doSomething` today, but you may call another function tomorrow and call an internal function next month. You don't want to rewrite your tests everytime. – a better oliver May 30 '18 at 13:47
  • 1
    Thats why mocking/spying is perfect. Because i indeed don't care about the internals during testing. I simply want to cover the possible outcomes so i can unit test my function properly. Let's turn it around. Why would you not mock/spy something? – user7995820 May 31 '18 at 11:41

2 Answers2

39

In your spec file import the helper this way:

import * as helper from './helper';

And in your it() you can spy on the helper object and return the requested value:

spyOn(helper, 'doSomething').and.returnValue({});
DarthJedi
  • 514
  • 4
  • 2
1

It is not possible to mock an external function, but you can do something like following which is working fine.

import { doSomething } from './helper';

@Component({
    ...
})
export class AppComponent {
    const doSomethingRef = doSomething; 
    test() {
        const data = this.doSomethingRef(1);
        return data.something ? 1: 2;
    }
}

Now, since we can mock doSomethingRef

describe('AppComponent ', () => {
  let appComponent: AppComponent ;
  beforeEach(() => {
    TestBed.configureTestingModule({});
    appComponent= TestBed.inject(AppComponent);
  });

  it('should allow mocking', () => {
    (appComponent as AppComponent).doSomethingRef = jasmine.createSpy('doSomethingRef ').and.returnValue(1);
    expect(guard.test()).toEqual(1);
  });
 }
RazvanParautiu
  • 2,805
  • 2
  • 18
  • 21