DEV Community

Cover image for Mocking dependencies with Jest + TypeScript
Laura Viglioni
Laura Viglioni

Posted on

Mocking dependencies with Jest + TypeScript

Once I've heard a phrase that was something like

Pure applications can only heat machines

And well... It that is true. In real life, some parts of our application need to have contact with the external world, to be impure, and testing them might be tricky.

wait, what are (im)pure functions and side effects?

Context

One common use is setting up feature flags for your application. Imagine you have the following function:

// file.ts
import { getFlag } from 'some-lib/flags'

export const myInpureFunction = () => {
  const flag = getFlag('flagName') as boolean
  return flag
    ? "this aplication is flagged"
    : "this application is not flagged"
}
Enter fullscreen mode Exit fullscreen mode

And you need to test it, after all, you must guarantee your feature flag is behaving as you expected in your project. The problem is: we need to mock this getFlag function and what happens when its value changes.

Well... What is the problem, then?

The usual way of mocking functions/modules are:

// file.spec.ts

jest.mock('some-lib/flags', ()=>({
  getFlag: () => true // or false or any other value
})
Enter fullscreen mode Exit fullscreen mode

But we have two contexts to cover with tests:

// file.spec.ts

describe('when feature flag is on', () => {...})

describe('when feature flag is off', () => {...})
Enter fullscreen mode Exit fullscreen mode

And we need to change the getFlag mock.

In JavaScript, we can implement solutions like:

// file.spec.ts
import { getFlag } from 'some-lib/flags'

jest.mock('some-lib/flags')

describe('when feature flag is on', () => {
  getFlag.mockReturnValue(true)
  //...
})
Enter fullscreen mode Exit fullscreen mode

Or using mockImplementation but none of these is allowed in TypeScript.

I've been across similar solutions like

// file.spec.ts
import * as flags from 'some-lib/flags'

jest.mock('some-lib/flags')

describe('when feature flag is on', () => {
  flags.getFlag = jest.fn()
  //...
})
Enter fullscreen mode Exit fullscreen mode

But TypeScript won't allow this either.

Solution

There is a way.

Have you ever head the tragedy of Type Assertion the wise?

This is not a very intuitive solution, in my opinion, but once you see it, is easy to understand:

// file.spec.ts
import { getFlag } from 'some-lib/flags'

jest.mock('some-lib/flags')

describe('when feature flag is on', () => {
  beforeEach(() => {
    (getFlag as jest.Mock).mockReturnValueOnce(true);
  });

  //...
})
Enter fullscreen mode Exit fullscreen mode

Asserting our name getFlag to jest.Mock type will allow us to use jest mock functions like mockReturnValueOnce.

In this case, any test inside this describe will use the true value from our mock, I think putting it inside a beforeEach block gives us more control and readability of what is happening, but you can put it inside an it block too.

Using mockReturnValueOnce instead of mocking implementation or return is a good practice because its changes will not affect any other test, be very careful with tests' side effects, they can lead you to trouble finding why sometimes your tests' suits pass and sometimes don't.

Well,

I do hope this is useful to you :)

Be safe, stay home, use masks and use Emacs (even though this is not an Emacs text haha)
Xoxo

Top comments (3)

Collapse
 
viglioni profile image
Laura Viglioni

Thanks a lot!

Collapse
 
jackmellis profile image
Jack

I'm a big believer in using composition patterns or dependency injection libraries to completely avoid the issue, rather than hacking node's internal require mechanics

Collapse
 
viglioni profile image
Laura Viglioni

I recently had a problem while testing a NEST application where I had to check if some methods were using a specific decorator, it was a hell on earth, I'm not really a fan of dependency injection and this is one of the reasons haha

But in this text case, sometimes in a React application at some point, you need to have a component that talks with the external world, like in the example LaunchDarkly, and sometimes you can't just pass the function as a parameter, in my simplistic example here yes, but in real life not always, for instance, if this were a ReactHook, you can only call it in some specific points etc

To be frank this was the motivation I had to write this text