I am trying to add unit tests for my Nest app that uses typeorm. My user service has a mock repository set up like this:
const mockUsersRepository = {
create: jest.fn().mockImplementation((dto) => dto),
insert: jest.fn((user) => Promise.resolve({ id: 1, ...user })),
findOneOrFail: jest.fn(({ where: { id } }) =>
Promise.resolve({
id: id,
firstName: 'John',
lastName: 'Smith',
created: '2022-08-18T04:43:26.035Z',
}),
)}
This works fine as a simple test to see if my service can return a user based on an ID passed in to it. The problem is that I use the findOneOrFail method in other endpoints like save like this:
async save(createUserDto: CreateUserDto): Promise<User> {
const newUser = this.usersRepository.create(createUserDto);
const insertedRecord = await this.usersRepository.insert(newUser);
return this.findOneOrFail(insertedRecord.identifiers[0].id);
}
Since the save returns the findOneOrFail method, if I want to test this save method it will always return the value that I set in the mock repository above so I can't test if it will return a new save value or if it returns a new updated value. Am I supposed to make the mock findOneOrFail more generic to handle if it's used in multiple endpoints?
You are creating the mock in your code here:
const mockUsersRepository = {
create: jest.fn().mockImplementation((dto) => dto),
insert: jest.fn((user) => Promise.resolve({ id: 1, ...user })),
findOneOrFail: jest.fn(({ where: { id } }) =>
Promise.resolve({
id: id,
firstName: 'John',
lastName: 'Smith',
created: '2022-08-18T04:43:26.035Z',
}),
)}
The Promise.resolve is what is returning the data for you. I do this a little different, and use the #golevelup/ts-jest library for easy mocking. I highly recommend this for this type of testing.
import { createMock } from '#golevelup/ts-jest'; // Using this to very easily mock the Repository
The beforeEach to create your mock
beforeEach(async () => {
...
const mockUsersRepository = createMock<usersRepository>(); // Create the full mock of the TypeORM Repository
...
In the actual test then, the simple negative test for an exception being thrown:
it('should error out if ...', async () => {
jest.spyOn(mockUsersRepository, 'create').mockImplementation( () => {
throw new Error('Mock error'); // Just error out, contents are not important
});
expect.assertions(3); // Ensure there are 3 expect assertions to ensure the try/catch does not allow code to to bypass and be OK without an exception raised
try {
await service.save(...);
} catch (Exception) {
expect(Exception).toBeDefined();
expect(Exception instanceof HttpException).toBe(true);
expect(Exception.message).toBe('Mock error');
}
});
});
In another test, that requires two steps, you can use the mockResolvedValueOnce() multiple times. For instance, maybe the call to the actual database function needs to read twice, once that it does not exist, and then once that it does. This next test covers that the code being called will do 2 findAll calls.
mockResolvedValueOnce('A').mockResolvedValueOnce('B').mockResolvedValueOnce('C') will return the 'A' for the first call, 'B' for the second, and 'C' for the 3rd and any additional calls. You can also chain mockRejectValueOnce() and others.
In your save function, what I have found is you should simply test the save() function, and have the usersRepository mocked. This way you only need to test the return from this function, without all the work against a data source. Therefore, the mock is on the save() and you simply return the instance of the User that you want.
async save(createUserDto: CreateUserDto): Promise<User> {
const newUser = this.usersRepository.create(createUserDto);
const insertedRecord = await this.usersRepository.insert(newUser);
return this.findOneOrFail(insertedRecord.identifiers[0].id);
}
However, if you want to mock just the storage calls, then the usersRepository is what you need to mock. Then you need to create the mockResolvedValueOnce (or mockResolvedValue() for all calls), against the .create() method, something against the insert() method, and then something against the findOneOrFail() method. All of these are called, so you mock needs to handle all 3.
In these examples, the mockResolvedValueOnce() is simply declaring the return, without having to do the implementation you have done via jest.fn, and mockImplementation.
Related
I have learned that a function with an asynchronous call (e.g. query to a database) is marked as await and the whole function block as async. However, I can apparently define some asynchronous functions without await and call them later with await (in my controller) and for others I am forced immediately to use await in my service class (VSC editor).
I have a user service class with CRUD operations. I can define findOne(), create() and find() without await, even though they perform asynchronous operations. In the controller I use them with async-await and I don't get an error from VSC even if I forget it. However, I have to use my update() and remove() functions in my service class with await because VSC shows me an error and says that I am missing await. Why do the update() and remove() functions have to be immediately marked with await and the others three do not? The functions save(), findOne() and find() have the same Promise return value as my other two functions and access the same repository.
My code (service class):
#Injectable()
export class UsersService {
constructor(#InjectRepository(User) private repo: Repository<User>) {}
create(email: string, password: string) {
const user = this.repo.create({ email, password });
return this.repo.save(user);
}
findOne(id: number) {
return this.repo.findOne(id);
}
find(email: string) {
return this.repo.find({ email });
}
async update(id: number, attrs: Partial<User>) {
const user = await this.findOne(id);
if (!user) {
throw new NotFoundException('user not found');
}
Object.assign(user, attrs);
return this.repo.save(user);
}
async remove(id: number) {
const user = await this.findOne(id);
if (!user) {
throw new NotFoundException('user not found');
}
return this.repo.remove(user);
}
}
Where is the difference and should I then rather always mark all my CRUD operations in the service class immediately as async-await in order to be able to call them later in the controller without async-await?
PS: Sorry if my text is still written too confusing. Why do I have to write await this.findOne() in the function remove(), but I can use this function findOne() with this.repo.findOne(id) without await in the same class, although repo.findOne() is an asynchronous function?
You need to use await because you want the value resolved by the promise returned by this.findOne(id)
And this.repo.find() will return a promise as it's async, thus UsersService#findOne returns a Promise too. So:
return await this.repo.findOne(id) will behave the same as:
return this.repo.findOne(id)
Learn about async/await:
https://nodejs.dev/learn/modern-asynchronous-javascript-with-async-and-await
https://javascript.info/async-await
https://jakearchibald.com/2017/await-vs-return-vs-return-await
I'm getting a little problem which I'm not being capable to debug. I wrote a little Firebase Function to get data from a JSON object and to store it in a Firestore Document. Simple.
It works, except the first time I run it after deployed (or after a long time has passed since the last execution). I have to run it once (without working), and then the subsequent tries always work, and I can see the new document being created with all the data inside it.
In the first attempt, there are no logs: Function execution took 601 ms, finished with status code: 200. Despite that, no document is being created nor changes being made.
In the second and subsequent attempts, If I request the function execution with a HTTP POST to https://cloudfunctions/functionName?id=12345, then the document '12345' is created inside collection with all the data inside it.
The collection where the documents are stored (scenarios) already exist in the database before any function call is executed.
This is the code:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
const db = admin.firestore();
db.settings({ignoreUndefinedProperties: true});
const fetch = require("node-fetch");
let scenarioData;
const fetchScenarioJSON = async (scenarioId) => {
try {
const response = await fetch(`https://url/api/scenarios/single/${scenarioId}`);
const scenarioText = await response.text();
scenarioData = JSON.parse(scenarioText);
} catch (err) {
return ("not valid json");
}
return scenarioData;
};
/**
* Add data to Firestore.
* #param {JSON} scenario JSON array containing the scenario data.
*/
async function addDataToFirestore(scenario) {
const data = {
id: scenario.scenario._id,
name: scenario.scenario.name,
description: scenario.scenario.description,
language: scenario.scenario.language,
author: scenario.scenario.author,
draft: scenario.scenario.draft,
last_modified: scenario.scenario.last_modified,
__v: scenario.scenario.__v,
duration: scenario.scenario.duration,
grade: scenario.scenario.grade,
deleted: scenario.scenario.deleted,
view_count: scenario.scenario.view_count,
comments_count: scenario.scenario.comments_count,
favorites_count: scenario.scenario.favorites_count,
activities_duration: scenario.scenario.activities_duration,
activities: scenario.scenario.activities,
outcomes: scenario.scenario.outcomes,
tags: scenario.scenario.tags,
students: scenario.scenario.students,
created: scenario.scenario.created,
subjects: scenario.scenario.subjects,
};
const res = await db.collection("scenarios").doc(scenario.scenario._id).set(data);
}
exports.functionName =
functions.https.onRequest((request, response) => {
return fetchScenarioJSON(request.query.id).then((scenario) => {
if (typeof scenario === "string") {
if (scenario.includes("not valid json")) {
response.send("not valid json");
}
} else {
addDataToFirestore(scenario);
response.send(`Done! Added scenario with ID ${request.query.id} to the app database.`);
}
});
});
My question is if I am doing anything wrong with the code that makes the execution not work on the first call after it is deployed, but actually does work in subsequent calls.
It is most probably because you don't wait that the asynchronous addDataToFirestore() function is completed before sending back the response.
By doing
addDataToFirestore(scenario);
response.send()
you actually indicate (with response.send()) to the Cloud Function platform that it can terminate and clean up the Cloud Function (see the doc for more details). Since you don't wait for the asynchronous addDataToFirestore() function to complete, the doc is not written to Firestore.
The "erratic" behaviour (sometimes it works, sometimes not) can be explained as follows:
In some cases, your Cloud Function is terminated before the write to Firestore is fully executed, as explained above.
But, in some other cases, it may be possible that the Cloud Functions platform does not immediately terminate your CF, giving enough time for the write to Firestore to be fully executed. This is most probably what happens after the first call: the instance of the Cloud Function is still running and then the docs are written with the "subsequent calls".
The following modifications should do the trick (untested). I've refactored the Cloud Function with async/await, since you use it in the other functions.
// ....
async function addDataToFirestore(scenario) {
const data = {
id: scenario.scenario._id,
name: scenario.scenario.name,
description: scenario.scenario.description,
language: scenario.scenario.language,
author: scenario.scenario.author,
draft: scenario.scenario.draft,
last_modified: scenario.scenario.last_modified,
__v: scenario.scenario.__v,
duration: scenario.scenario.duration,
grade: scenario.scenario.grade,
deleted: scenario.scenario.deleted,
view_count: scenario.scenario.view_count,
comments_count: scenario.scenario.comments_count,
favorites_count: scenario.scenario.favorites_count,
activities_duration: scenario.scenario.activities_duration,
activities: scenario.scenario.activities,
outcomes: scenario.scenario.outcomes,
tags: scenario.scenario.tags,
students: scenario.scenario.students,
created: scenario.scenario.created,
subjects: scenario.scenario.subjects,
};
await db.collection("scenarios").doc(scenario.scenario._id).set(data);
}
exports.functionName =
functions.https.onRequest(async (request, response) => {
try {
const scenario = await fetchScenarioJSON(request.query.id);
if (typeof scenario === "string") {
if (scenario.includes("not valid json")) {
response.send("not valid json");
}
} else {
await addDataToFirestore(scenario); // See the await here
response.send(`Done! Added scenario with ID ${request.query.id} to the app database.`);
}
} catch (error) {
// ...
}
});
Imagine you are working with a database of national teams and athletes (like for the Olympic games). When testing your app, you create a new database every time. Here are two models:
const Team = sequelize.define('team', {
name: {type: Sequelize.TEXT, allowNull: false}
})
const Athlete = sequelize.define('athlete', {
name: {type: Sequelize.TEXT, allowNull: false}
})
Team.hasMany(Athlete)
Athlete.belongsTo(Team)
Now, you have a function that creates the teams for you:
function mock_database() {
let team0 = Team.create({
name: 'TeamGB'
}).then(team => {
generate_athletes(team)
// Or maybe I need to return the nested promises?
// return generate_athletes(team)
})
let team1 = Team.create({
name: 'TeamFr'
}).then(team => {
generate_athletes(team)
// Or maybe I need to return the nested promises?
// return generate_athletes(team)
})
return [team0, team1]
}
Now, the generate_athletes creates nested database objects (athletes in the team):
function generate_athletes(team) {
let athlete0 = Athlete.create({
name: 'John Smith'
}).then(athlete => {
team.setAthletes(athlete)
athlete.setTeam(team)
})
let athlete1 = Athlete.create({
name: 'Joanna Smith'
}).then(athlete => {
team.setAthletes(athlete)
athlete.setTeam(team)
})
return [athlete0, athlete1]
}
At this point, I would like to wait until the database has all the data. I can do that with Promise.all(mock_database()).then(() => {/*my application starts here*/}), which would be waiting on the two promises team0 and team1. But does that guarantee that I will be waiting on athlete0 and athlete1 implicitly too? Or do I need to return those promises back up, incorporate them all into a big array of [team0, team1, promises0, promises1] and wait on that?
You would need to return the nested promises. If you don't return the nested promise then your top-level promises will just resolve immediately after creating the teams (since creating the athletes is async and you aren't waiting for it to finish).
Essentially you would want something like this...
function mock_database() {
const team0 = Team.create({ // <-- team0::Promise
// ...
});
const team1 = Team.create({ // <-- team1::Promise
// ...
})
.then(team => {
return generate_athletes(team); // <-- Will only resolve team1 when this resolves
});
return Promise.all([team0, team1]); // Wait for both promises to resolve
}
function generate_athletes(team) {
const athlete1 = Athlete.create({ // athlete1::Promise
// ...
});
const athlete2 = Athlete.create({ // athlete2::Promise
// ...
});
return Promise.all([athlete1, athlete2]); // Wait for both promises to resolve
}
Unless you explicitly return the promises your program does not have access to the then and catch properties of the promise. So always remember, if you want to wait for a promise you will definitely need access to it! So always return it!
Good luck :)
EDIT: You can find a Promise.all explanation here. Essentially though what is happening is that each database action returns a promise. Those promises will resolve if and only if the database action is successful. Since promises are just regular JavaScript objects, they can be saved to a variable.
What I ended up doing was saving a reference to all of the promises I wanted to resolve and then used a builtin promise function to wait for the completion of all of the promises. Since the athletes depend on the team being created, for each team we also need to wait for the list of athletes to be generated.
I marked up the code with some more comments!
had following code which worked:
let sdk = new SDK({ name: "somevalue"});
module.exports= ()=> {
sdk.dothing();
}
I then needed to change the parameter to use data from an async function:
let asyncfunc = require('asyncfunc');
let sdk = new SDK({ name: (()=>{
return asyncfunc()
.then((data) => {
return data.value
})
});
module.exports= ()=> {
sdk.dothing();
}
Following the change, the call to new SDK is failing because the parameter passed is {} as the asyncFunc promise has not yet resolved.
I'm getting back into node after a year and new to promises. what is the proper way to do this?
As you've found, you can't pass in a promise to something that's expecting a string. You need to wait for the asynchronous operation to complete.
This means that your SDK won't be ready right away, so you have two options:
Change your module so it returns a promise for the needed value. Anyone who needs to use your module would need to use the returned promise.
Example:
let pSdk = asyncFunc()
.then(data => new SDK({ name: data.value }));
module.exports = () => pSdk.then(sdk => sdk.dothing());
Store an sdk value that's not populated immediately. Users of your module can obtain the SDK instance directly, but it might not be ready when they need it.
Example:
let sdk;
asyncFunc()
.then(data => sdk = new SDK({ name: data.value }));
module.exports = () => {
if(!sdk) { throw new Error("The SDK is not ready yet!"); }
return sdk.dothing();
};
if any bit of code, in node, is asynchronous then immediately next bit of code will be executed. it doesn't matter if the asynchronous code is wrapped in promise or not.( For codes wrapped in the promise the compiler will return a pending promise to be resoled or rejected and proceed to the next bit of code.) When you are creating an object using new SDK({ }) the name is having reference to a pending promise which is yet to be settled that's why your code is failing to fulfill your requirement. You can do it this way to resolve your problem.
asyncfunc()
.then((data) => {
return new SDK({ name: data.value });
}).then(function(sdk){
//do your work here using sdk
})
One important point to be noted here is you can't return from .then() to assign the value to any variable as you are doing. The value returned from .then() will be accessible from the next chained .then() not by outside global variable.Since you are exporting sdk.dothing() so you need to export it inside the last .then()
I want inject into constructor database client, but when I run tests, mocha throw exception, that method whitch is called is not a function.
export class CustomService {
constructor(database: any) {
database.init().then((res)=>{}));
}
}
describe('CRUD service', ()=>{
it('when i decide save item', ()=>{
let db = sinon.mock(new DatabaseService);
let instance = new CustomService(db);
db.expects('init').once();
db.verify();
});
});
In console:
TypeError: database.init is not a function
What is wrong?
Don't pass the return value of sinon.mock to the code you are testing but instead pass the original object you passed to sinon.mock. The return value of sinon.mock is only for setting assertions and checking them. You also need to set the order of the statements in your tests so that the expectations are set before the code that must satisfy them is run. Something like this:
describe('CRUD service', ()=>{
it('when i decide save item', ()=>{
const db = new DatabaseService();
let mock = sinon.mock(db);
mock.expects('init').once();
let instance = new CustomService(db);
mock.verify();
});
});