How to spyOn a function from the same module in jest? [duplicate] - javascript

This question already has answers here:
How to mock functions in the same module using Jest?
(10 answers)
Closed 6 months ago.
Consider this set of code snippets where I want to write unit tests for function A, which internally calls function B during execution. Assume B is a set of API calls for validation that I want to mock return value to true.
But this spyOn method approach does not work, and the API calls in B still get executed. I've seen approaches with mocking the complete module with jest.requireActual(), but they do not seem to work too.
What could be a better way to test such functions without changing a lot in the codebase index.ts?
//index.ts
async function A (a:string, b:string, c:string) {
// code
await B(data);
// code
}
async function B (data:customType) {
//code
//API calls
//code
}
export default {
A,
B }
//index.test.ts
import index from '../index.ts';
describe('Test suit', ()=>{
it('should be a test for function A', async ()=> {
jest.spyOn(index, 'B').mockReturnValue(true);
// code
const result = await index.A('a','b','c');
// code
})
})

There is not really a good way to do it, when the functions are in the same module with jest only.
But you can with Rewire, it's explained in this answer and worked very well in a similar situation for me: https://stackoverflow.com/a/52725067/16068019
Otherwise if it's an option you could move one of your functions to a different module, but that's not always an option.

Related

Is it possible to mock a free-standing function in Javascript with Jest?

There are many questions on StackOverflow about how to mock things with Jest. I have, indeed, read many of those suggestions. I have experience with jest.mock and spyOn. I've tried the trick of doing a real import and overwriting just the function I desire to mock and have also tried the old import * as math from './math.js' and then you try to spyOn math but if you try it, you'll see that this won't work.
But after quite a lot of experimentation, I'm beginning to conclude that this is fundamentally impossible (which actually feels correct to me). The "real" subtract function that calls add will only ever be able to call the add defined in that module.
KEY POINT: The math.js file can NOT be edited. You cannot change it so that you have add and subtract as properties hanging off of some object. Consider that the math.js file is etched in stone and cannot be modified in any way.
I would LOVE to be wrong about this, but it seems to me that the free-standing add function in the math.js file cannot be mocked such that when you call the "real" subtract function, it runs the mocked add function. I know you could mock add and have mock add call a mocked subtract, but that is not the goal here.
Please consider this a purely academic question. This is not an XY problem. This is not a case of "what are you trying to do; there is a better way." This is purely trying to understand how things work given the fixed constraints I've outline in the question. Thanks!
math.js
export function add(a, b) {
return a+b;
}
export function subtract(a, b) {
return add(a, -b);
}
math.test.js
import { subtract } from './math.js';
// mock the add function to return 1234
describe('math', () => {
it('should return 1234', () => {
expect(subtract(5,3)).toBe(1234);
});
});

What's the advantage of fastify-plugin over a normal function call?

This answer to a similar question does a great job at explaining how fastify-plugin works and what it does. After reading the explanation, I still have a question remaining; how is this different from a normal function call instead of using the .register() method?
To clarify with an example, how are the two approaches below different from each other:
const app = fastify();
// Register a fastify-plugin that decorates app
const myPlugin = fp((app: FastifyInstance) => {
app.decorate('example', 10);
});
app.register(myPlugin);
// Just decorate the app directly
const decorateApp = (app: FastifyInstance) => {
app.decorate('example', 10);
};
decorateApp(app);
By writing a decorateApp function you are creating your own "API" to load your application.
That said, the first burden you will face soon is sync or async:
decorateApp is a sync function
decorateAppAsync within an async function
For example, you need to preload something from the database before you can start your application.
const decorateApp = (app) => {
app.register(require('#fastify/mongodb'))
};
const businessLogic = async (app) => {
const data = await app.mongo.db.collection('data').find({}).toArray()
}
decorateApp(app)
businessLogic(app) // whoops: it is async
In this example you need to change a lot of code:
the decorateApp function must be async
the mongodb registration must be awaited
the main code that loads the application must be async
Instead, by using the fastify's approach, you need to update only the plugin that loads the database:
const applicationConfigPlugin = fp(
+ async function (fastify) {
- function (fastify, opts, next) {
- app.register(require('#fastify/mongodb'))
- next()
+ await app.register(require('#fastify/mongodb'))
}
)
PS: note that fastify-plugin example code misses the next callback since it is a sync function.
The next bad pattern will be high hidden coupling between functions.
Every application needs a config. Usually, the fastify instance is decorated with it.
So, you will have something like:
decorateAppWithConfig(app);
decorateAppWithSomethingElse(app);
Now, decorateAppWithSomethingElse will need to know that it is loaded after decorateAppWithConfig.
Instead, by using the fastify-plugin, you can write:
const applicationConfigPlugin = fp(
async function (fastify) {
fastify.decorate('config', 42);
},
{
name: 'my-app-config',
}
)
const applicationBusinessLogic = fp(
async function (fastify) {
// ...
},
{
name: 'my-app-business-logic',
dependencies: ['my-app-config']
}
)
// note that the WRONG order of the plugins
app.register(applicationBusinessLogic);
app.register(applicationConfigPlugin);
Now, you will get a nice error, instead of a Cannot read properties of undefined when the config decorator is missing:
AssertionError [ERR_ASSERTION]: The dependency 'my-app-config' of plugin 'my-app-business-logic' is not registered
So, basically writing a series of functions that use/decorate the fastify instance is doable but it adds
a new convention to your code that will have to manage the loading of the plugins.
This job is already implemented by fastify and the fastify-plugin adds many validation checks to it.
So, by considering the question's example: there is no difference, but using that approach to a bigger application
will lead to a more complex code:
sync/async loading functions
poor error messages
hidden dependencies instead of explicit ones

How to mock a nested function with Jest

I've tried a couple of approaches but I'm not sure if it's possible to mock a nested function in Jest. I read the official docs where they use axios, but I'm trying to mock a function that is imported, not a module.
for example, I have some function A, and within A I call B. I want to mock B so that it returns true once and then false once.
B is a function I imported that I wrote in another file, and I'm testing A.
so far I have tried to import function B into my test file and create a mock of it, but I don't think it's working how I thought it would
for example I have tried this
import { b } from "../util"
jest.mock("b", () => jest.fn().mockReturnValue(true));
and then my tested file would be
function a() => {
while b() {
do something;
}
}
should I rewrite my test or function I'm testing?
Not sure what kind of results you are getting, but for mocking b you could do:
jest.mock("../util", () => ({
b:jest.fn().mockReturnValueOnce(true).mockReturnValueOnce(false),
}));

Jasmine Async test generation

let's imagine we have a promise that does a large amounts of operations and return helper functions.
A banal example:
const testPromise = testFn => () => {
const helper = Promise.resolve({testHelper: () => 'an helper function'}) // I/O Promise that returns an helper for testing
return helper.then(testFn).finally(() => console.log('tear down'));
}
// This describe would work as expected
describe('Async test approach', () => {
it('A test', testPromise(async ({testHelper}) => {
expect(testHelper()).toBe('an helper function')
}))
})
// This part doesn't work
describe('Async describe approach', testPromise(async ({testHelper}) => {
it('Test 1', () => {
expect(testHelper()).toBe('an helper function')
})
it('Test 2', () => {
expect(testHelper()).not.toBe('A chair')
})
}))
}
What I would like to achieve is something like the second example where I can use async code within describe without re-evaluating testPromise.
describe doesn't handle async so I am not even able to loop and create dynamic tests properly.
I did read many comments around saying that describe should only be a simple way to group tests but... then... how can someone make async generated tests based on I/O result?
Thanks
= ADDITIONAL CONSIDERATION =
Regarding all the comment you guys kindly added, I should have added few additional details...
I am well aware that tests must be defined synchronously :), that is exactly where problems starts. I totally disagree with that and I am trying to find an alternative that avoids before/after and doing it without specifying an external variable. Within Jest issues there was an open one to address that, it seems they did agree on making describe async but they won't do it. The reason is... Jest is using Jasmine implementation of describe and this "fix" should be done in there.
I wanted to avoid beforeAll and afterAll, as much as I could. My purpose was creating an easy (and neat) way to define integration tests tailored on my needs without letting users to worry about initialize and tear down stuff around. I will continue to use the Example 1 above style, that seems the best solution to me, even if it would be clearly a longer process.
Take a look at Defining Tests. The doc says:
Tests must be defined synchronously for Jest to be able to collect your tests.
This is the principle for defining test cases. Which means the it function should be defined synchronously. That's why your second example doesn't work.
Some I/O operations should be done in beforeAll, afterAll, beforeEach, afterEach methods to prepare your test doubles and fixtures. The test should be isolated from the external environment as much as possible.
If you must do this, maybe you can write the dynamically obtained testHelper function to a static js file, and then test it in a synchronous way
As it was noted, describe serves to group tests.
This can be achieved with beforeAll. Since beforeAll should be called any way, it can be moved to testPromise:
const prepareHelpers = (testFn) => {
beforeAll(() => {
...
return helper.then(testFn);
})
}
describe('Async describe approach', () => {
let testHelper;
prepareHelpers(helpers => { testHelper = helpers.testHelper });
...

How to get/set out-of-scope variables when testing a function with dependency on these (jest)?

I'm trying to have full test coverage on some helper functions in my project. My first tests for either of the functions checks whether 2 out-of-scope variables are declared or not, which run successfully.
What I want to do is, jest to set/mock wrapper and layer variables declared out-of-scope of the functions to be tested, but can't seem to have it right no matter how hard I tried.
Thankful and appreciate any kind of help in this direction as I'm fairly rookie in terms of writing tests in general not to mention tests for jest.
I have tried jest.mock by mocking by default my imported module and then did jest.requireActual on 2 functions.
I have tried jest.doMock with a module factory which sets these 2 variables and then returns in an object.
Module: helpers/index.js
let wrapper; // Is created on runtime ergo initially undefined.
const layer = document.createElement('div');
layer.classList.add('layer');
const isElement = elm => {
if (!(elm instanceof Element)) {
throw new Error('Given element is not of type Element or is undefined!');
}
}
export const someFn = async () => {
wrapper = document.querySelector('.wrapper');
isElement(wrapper);
isElement(layer);
// Code that needs to be tested
// but cannot since when testing this function
// 'wrapper' and 'layer' are always undefined.
}
Test: helpers/index.test.js
// Helpers
import {someFn} from 'helpers';
describe('Method: someFn', () => {
test('should NOT throw', async () => {
await expect(someFn()).resolves.not.toThrow(); // but it does!
});
});
Actual result: Received promise rejected instead of resolved
Expected result: Not to throw, considering these out-of-scope variables that this function relies on, can somehow be set/mocked while testing it.

Categories

Resources