I have this node module called client. It has the following structure:
//index.js
import users from "./users"
export default { users }
And:
//users.js
export default { getUser }
function getUser(username) {
...
return {role: userRole}
}
I want to mock this getUser(username) function in my tests.
So I could call something like:
client.users.getUser.mockResolvedValueOnce({role: "manager"})
My test header is like:
let client = jest.genMockFromModule('client').default;
client.users.getUser = jest.fn();
But, running my test, I get the following error when my original code call client.users.getUser.
TypeError: Cannot read property 'users' of undefined
58 |
59 | // Retrieve user and then return its projects
> 60 | client.users.getUser(username)
| ^
61 | .then(user => {
62 | if (!user) {
63 | throw new Error(`User ${username} not found`)
at Object.getUser (node_modules/client/build/users.js:26:45)
at Object.getUser [as insert] (src/web/controller/projects.js:60:18)
at Object.insert (src/web/controller/projects.test.js:80:18)
You can just mock the //users.js module like this:
jest.mock('./users.js', () => ({getUser: () => ({role: "manager"})})) //note that the path is relative to the test
if you need different return values during your tests you can mock it to return a spy and set the mock return value in the tests:
import {getUsers} from './users'
jest.mock('./users.js', () => ({getUser: jest.fn()}))
it('does something', () => {
getUser.mockImplementation(() => ({role: "manager"}))
})
it('does something else', () => {
getUser.mockImplementation(() => ({role: "user"}))
})
Related
I would like to test following part of the code:
// ... code above
const created = async payload => {
const model = await db.collection('models').doc(payload.model)
.get() // <--- 1st .get() occurence
if (!model.exists) {
// Add product to the orphans collection
await db.collection('orphans').doc(payload.sku).set(payload)
} else {
// Grab the categories field
const categories = model.get('categories') // <--- 2nd .get() occurence
// Product is either empty or does not exists at all
if (!categories || categories.length < 1) {
// Add product to the orphans collection
await db.collection('orphans').doc(payload.sku).set(payload)
} else {
// Otherwise remove from the orphans collection
await deleted(payload.sku)
}
}
}
I do not know how to properly mock the file twice in the same callback. Here is what I get:
test.only('it should react when an event "created" has been fired', async () => {
const spy = jest.fn()
jest.doMock('#google-cloud/firestore', () => class {
collection () {
return {
doc: () => {
return {
get: () => {
return {
exists: () => {
spy()
}
}
},
set: () => {
spy()
}
}
}
}
}
})
const observer = require('./product')
await observer('created', {})
await expect(spy.mock.calls.length).toBe(1)
})
I get this error:
● it should react when an event "created" has been fired
TypeError: model.get is not a function
25 | } else {
26 | // Grab the categories field
> 27 | const categories = model.get('categories')
| ^
28 |
29 | // Product is either empty or does not exists at all
30 | if (!categories || categories.length < 1) {
at created (app/observers/product.js:27:30)
at Object.<anonymous>.module.exports (app/observers/product.js:6:28)
at Object.<anonymous> (app/observers/product.spec.js:34:3)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 skipped, 2 total
Snapshots: 0 total
Time: 0.147 s, estimated 1 s
Ran all test suites matching /app\/observers\/product.spec.js/i.
What is the working solution to test two scenarios of the same mocked get() method ?
In your code :
const model = await db.collection('models').doc(payload.model)
.get() // <--- 1st .get() occurence
If we look at your mock, the get method of doc returns :
{
exists: () => {
spy()
}
}
There are no property named get, so it is undefined (and not a function).
I guess you just have to change this part to :
{
exists: true, // can be false
get: spy,
}
And your problem should be solved.
Btw, you can also change the mock of set method to set: spy. Or you can keep it to set: () => { spy() }, but you should at least return the value if you want to mock it : set: () => { spy() }.
Now, about how to properly mock multiple times, here's what you can do :
const observer = require('./product')
const spyGet = jest.fn()
const spySet = jest.fn() // I like having different mocks, if one function use get & set, tests will be clever & more readable if you use different spies
describe('on event "created" fired', () => {
const categories = []
beforeEach(() => {
// I put mocks here to make test more readable
jest.doMock('#google-cloud/firestore', () => class {
collection () {
return {
doc: () => {
return {
get: () => {
return {
exists: true,
get: spyGet,
}
},
set: spySet
}
}
}
}
})
spyGet.mockResolvedValueOnce(categories) // you can also use mockResolvedValue, but mockResolvedValueOnce allow you to mock with different values on the same test & same mock
})
it.only('should get categories', async () => {
await observer('created', {})
// here's all the ways you can test it
expect(spyGet).toBeCalledTimes(1)
expect(spyGet.mock.calls.length).toBe(1)
expect(spyGet).toBeCalledWith('categories')
expect(spyGet).toHaveBeenNthCalledWith(1, 'categories')
})
})
Note : You should reset & clear your mocks between tests manually (in a afterEach or beforeEach) if you don't set it into jest config.
I'm trying to do this test, for a simple function, but I can't. I posted my test code and error.
I tried to do it in several different ways but I was not successful.
I'm using NestJS CLI and the test using jestJs
// My coding
createSession(login: string, password: string) {
const search: UserEntity = this.users.find(
(user: UserEntity) => user.cpf === login,
);
if (search) return search.senha === password;
else throw new HttpException('UNAUTHORIZED', HttpStatus.UNAUTHORIZED);
}
// My test
it('should retrieve getHello', async () => {
await expect(
service.createSession(mockLogin.login, mockLogin.password),
).rejects.toEqual(
new HttpException('UNAUTHORIZED', HttpStatus.UNAUTHORIZED),
);
});
// The error
● LoginService › Get service › should retrieve getHello
HttpException: UNAUTHORIZED
22 | );
23 | if (search) return search.senha === password;
> 24 | else throw new HttpException('UNAUTHORIZED', HttpStatus.UNAUTHORIZED);
| ^
25 | }
26 | }
27 |
I managed to solve it as follows:
it('should retrieve', async () => {
await expect(
service.createSession(mockLogin.login, mockLogin.password),
).rejects.toThrow();
});
I'm trying to replace a static method of a class so that it returns custom value for testing purposes.
I have multiple test (describe) blocks in my file, and my goal is to replace the static method only in one of the describe blocks.
The problem is that the method is replaced for all the tests, even though I have a teardown method that is replacing the static method with the original method.
Some code:
class Cat {
static sound() {
return "Meow";
}
}
module.exports = {
Cat
}
const sinon = require("sinon");
const { Cat } = require("./myClass");
describe("Main tests", () => {
describe("Test Cat", () => {
it("Meows", () => {
expect(Cat.sound()).toBe("Meow")
})
})
describe("Test Dog", () => {
const stub = sinon
.stub(Cat, "sound")
.callsFake(() => "Woof");
afterAll(() => {
stub.restore();
});
it("Barks", () => {
expect(Cat.sound()).toBe("Woof")
})
})
})
Test results - the unreplaced test case is failing:
FAIL ./index.test.js
Main tests
Test Cat
✕ Meows (6ms)
Test Dog
✓ Barks
● Main tests › Test Cat › Meows
expect(received).toBe(expected) // Object.is equality
Expected: "Meow"
Received: "Woof"
7 | describe("Test Cat", () => {
8 | it("Meows", () => {
> 9 | expect(Cat.sound()).toBe("Meow")
| ^
10 | })
11 | })
12 |
Is there a way how to prevent this?
I tried using createSandbox:
const sandbox = sinon.createSandbox()
const stub = sandbox
.stub(Cat, "sound") // etc
but it's the same thing.
Any help would be appreciated.
This task can be done easily with jestjs only (without sinon).
Just use jest.spyOb function to spy sound function, and you can mock the result of this function:
const { Cat } = require('./myClass');
describe('Main tests', () => {
beforeEach(() => {
jest.spyOn(Cat, 'sound');
});
afterEach(() => {
jest.resetAllMocks();
});
describe('Test Cat', () => {
it('Meows', () => {
// Don't mock, just call actual logic
expect(Cat.sound()).toBe('Meow');
});
});
describe('Test Dog', () => {
it('Barks', () => {
Cat.sound.mockReturnValue('Woof'); // mock return value of `sound()`
expect(Cat.sound()).toBe('Woof');
});
});
});
I am learning NodeJs and Jest. I am having trouble with unit tests. I just translated my actual code to a simple logic. I have two files as below.
// age.js
function getAge(birthYear) {
const age = 2021-birthYear;
return age
}
module.exports = { getAge }
// user.js
const { getAge } = require("./age");
async function isMinor(){
const bYear = 1991
const age = await getAge(bYear)
if( age <= 18) {
return true
}
return false
}
module.exports = { isMinor }
isMinor calls getAge from another file, I want to test isMinor without actually calling getAge. I referred to this article and wrote my test, but I still encountered some issues.
// user.test.js
const { isMinor } = require("./user")
describe("Age Test", () => {
// Question 1: how can I properly import getAge function here and mock a return value for it? I also tried mockImplementation and mockReturnedValue, but they didn't work
// I don't want to actually invoke getAge function
beforeEach(() => {
jest.mock("./age", () => ({
getAge: () => 99,
}))
})
// Question 2: How can I teardown the moch after the test
afterEach(() =>{
getAge.mockRestore()
})
test("should be an adult", async () => {
const isMinor = await isMinor();
expect(isMinor).toEqual(false);
});
});
I expect to receive 99 from getAge, but it returns null. I appreciate any helps. Thank you.
Since you're only testing isMinor with mock values you'll want to test it with multiple values to cover all of the different scenarios (branches), so you can create a mock for the ./age.js only once by simply calling:
const { getAge } = require('./age');
jest.mock('./age');
It will generate a mock function for each module function only for this test file
Modules that are mocked with jest.mock are mocked only for the file that calls jest.mock. Another file that imports the module will get the original implementation even if it runs after the test file that mocks the module.
So there will be no need for you to restore the original implementation.
The biggest advantage from using auto-mocks is when the method from the implementation (in this case getAge) is removed - the test will fail.
The only thing left to do would be to set the mock's return value that you want to test with. And since it's expected to return a promise you should use .mockResolvedValue()
user.test.js
const { isMinor } = require("./user");
const { getAge } = require('./age');
jest.mock('./age');
describe("Age Test", () => {
describe('getAge returning more than 18', () => {
beforeAll(() => {
getAge.mockResolvedValue(99)
})
test("should be an adult", async () => {
expect(await isMinor()).toEqual(false);
});
})
describe('getAge returning less than 18', () => {
beforeAll(() => {
getAge.mockResolvedValue(13)
})
test("should be a minor", async () => {
expect(await isMinor()).toEqual(true);
});
})
});
Working example
Below example use "jest": "^26.6.3".
user.js:
const { getAge } = require('./age');
async function isMinor() {
const bYear = 1991;
const age = await getAge(bYear);
console.log('age: ', age);
if (age <= 18) {
return true;
}
return false;
}
module.exports = { isMinor };
Option 1: use jest.mock() in beforeEach hook functional scope, it will NOT be hoised to the top of the code. So you need to require modules after mocking by jest.mock() method.
describe('Age Test', () => {
beforeEach(() => {
jest.mock('./age', () => ({
getAge: jest.fn(() => 99),
}));
});
test('should be an adult', async () => {
const { isMinor } = require('./user');
const { getAge } = require('./age');
const actual = await isMinor();
expect(actual).toBeFalsy();
expect(getAge).toBeCalledWith(1991);
});
});
unit test result:
PASS examples/66288290/user.test.js
Age Test
✓ should be an adult (1911 ms)
console.log
age: 99
at examples/66288290/user.js:6:11
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 87.5 | 50 | 100 | 87.5 |
user.js | 87.5 | 50 | 100 | 87.5 | 8
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 3.197 s
Option 2: use jest.mock() in the module scope, it will be hoisted to the top of the code. Even if you require the modules at the top of the file. The ./age module you require is already be mocked.
const { isMinor } = require('./user');
const { getAge } = require('./age');
jest.mock('./age', () => ({
getAge: jest.fn(() => 99),
}));
describe('Age Test', () => {
afterAll(() => {
jest.resetAllMocks();
});
test('should be an adult', async () => {
const actual = await isMinor();
expect(actual).toBeFalsy();
expect(getAge).toBeCalledWith(1991);
});
});
unit test result:
PASS examples/66288290/user.test.js
Age Test
✓ should be an adult (11 ms)
console.log
age: 99
at examples/66288290/user.js:6:11
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 87.5 | 50 | 100 | 87.5 |
user.js | 87.5 | 50 | 100 | 87.5 | 8
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 3.502 s
This test:
import { render } from '#testing-library/svelte';
import { setRoutes } from '../router';
import SLink from '../router/components/SLink.svelte';
import routes from '../routes';
beforeAll(() => setRoutes(routes));
test('Instantiates components', () => {
expect(() => render(SLink, { props: { name: 'About' } })).not.toThrow();
});
Produces this error:
Error name: "TypeError"
Error message: "Cannot read property 'forEach' of undefined"
137 |
138 | const matchRoute = passedRoutes => {
> 139 | passedRoutes.forEach(compare => {
| ^
140 | if (matchedRoute) return;
141 |
142 | const { regex, fullRegex } = compare as FormattedRoute;
Which comes from this function:
const compareRoutes = (routes, route) => {
let matchedRoute;
const matchRoute = passedRoutes => {
passedRoutes.forEach(compare => {
// ...
});
};
matchRoute(routes);
return matchedRoute;
};
Which is called by the component I'm trying to render in the test:
<script>
import { routes } from '../logic';
import { compareRoutes } from '../static';
export let name;
// Error stems from 'routes' being undefined here
const route = compareRoutes(routes, { name });
</script>
This line:
beforeAll(() => setRoutes(routes));
Sets the routes imported by SLink which are then passed to compareRoutes, so they shouldn't be undefined.
I've used the same line for other functions and the tests run as expected.
Can #testing-library/svelte not resolve imports? Or, is there another reason for this?
setRoutes:
let routes;
const setRoutes = (userRoutes) => {
// ...
routes = userRoutes;
};
export { routes };
I guess this is rendered in JSDom, and I suspect it might not support live bindings like what you're doing with your export { routes }.
Try exporting a function instead, to confirm:
export const getRoutes = () => routes
Also, this is off topic, but it really feels like your compareRoutes function could be simplified as a filter (or map, or reduce) operation. Something like that:
const compareRoutes = (routes, route) => routes.filter(
x => route.path.startsWith(x.path)
)