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"
}
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
})
But we have two contexts to cover with tests:
// file.spec.ts
describe('when feature flag is on', () => {...})
describe('when feature flag is off', () => {...})
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)
//...
})
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()
//...
})
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);
});
//...
})
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)
Thanks a lot!
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
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