Cannot get mockImplementation to work for mocked module - javascript

I am trying to properly test this File, however osme test can be done when mocking entirre module, but some i need only specific methods mocked , so i tried combinaitons of multiple things, but currently one way I am attemping to do this is for this one test below
// features.js
const getFeature = (featureFlag) => {
const client = getFeatureInstance();
const feature = client.getFeature(featureFlag);
return retrieveFeature(feature);
};
getFeatureInstance and retrieveFeature are local to same file, client is something i need to manually implement in the mock returned by getFeatureInstance
// features.unit.spec.js
const {
getFeatureInstance,
} = require('../../../../../src/server/features');
jest.mock('../../../../../src/server/features');
describe('server:apis:feature:management', () => {
it('should return feature', () => {
// jest.resetAllMocks();
const management = jest.requireActual('../../../../../src/server/features');
const _featureId = 'FAKE-FEATURE';
getFeatureInstance.mockImplementation(() => ({
getFeature: jest.fn(featureId => ({ featureId }))
}));
const feature = management.getFeature(_featureId);
console.log(feature);
});
});
But no matter what I do, any method of testing, mocking implementaion of local modules doesnt work.
if you look at getFeatureInstance.mockImplementation, it does not return what i am telling it too, I went and logged the objects in the actual features.js` and in the test File.
Even if i remove getFeatureInstance.mockImplementation , the objects dont change.
From what i see, the mocking of the module works but no mockImplementation does anything per test
Now if I mocked the entire module and set the method in the factory, it works but i need different implementations per test

Did you try spying?
import * as features from '../../../../../src/server/features';
it('should return feature', () => {
spyOn(features, 'getFeatureInstance').mockReturnValue({
getFeature: jest.fn(featureId => ({ featureId }))
});
const feature = management.getFeature(_featureId);
...

Related

abstract mock API response using MockAdapter with axios

I am using MockAdapter with axios to mock api response in storybook
export const defaultAccountMockAPI = () => {
const mock = new MockAdapter(axiosInstance);
const defaultAccountDetails = objectKnob('Default Account Details', DefaultAccountDetails);
mock
.onGet(
'/services/api/account/1902124261/account-details',
)
.reply(() => {
return [200, defaultAccountDetails];
});
};
export const accoutMockAPI = () => {
const mock = new MockAdapter(axiosInstance);
const accountDetails = objectKnob('Account Details', AccountDetails);
mock
.onGet(
'/services/api/account/1902124221/account-details',
)
.reply(() => {
return [200, accountDetails];
});
};
Let's say I have above two methods which mock default account details and account details. The only difference between these two methods is different account id (1902124261/1902124221). I need to show 2 stories based on these 2 different accounts, how I can abstract the mock api method instead of for each story I need to write these duplicated codes(Apart from account id, I also have other parameters which have this issue as well.)
The README file of MockAdapter says that its possible to use a regular expression as url.
So with your code it should look like this:
mock
.onGet(/\/services\/api\/account\/.*?\/account-details/g)
.reply(() => {
return [200, accountDetails];
});
https://github.com/ctimmerm/axios-mock-adapter/blob/59258dd1e78531853c24a4cf2ed002455f66ecbf/README.md?plain=1#L138

What functions should I mock during unit testing

I've been reading some articles, and posts here on Stack Overflow about when I should mock a function and when I shouldn't, but I have a case where I'm not sure about what to do.
I have a UserService class which uses dependency injection concept to receive dependencies through its constructor.
class UserService {
constructor(userRepository) {
this.userRepository = userRepository;
}
async getUserByEmail(userEmail) {
// would perform some validations to check if the value is an e-mail
const user = await this.userRepository.findByEmail(email);
return user;
}
async createUser(userData) {
const isEmailInUse = await this.getUserByEmail(userData.email);
if(isEmailInUse) {
return "error";
}
const user = await this.userRepository.create(userData);
return user;
}
}
I want to test if the createUser method works properly, and for my tests, I created a fake userRepository which is basically a object with mocked methods that I will use while instantiating UserService Class
const UserService = require('./UserService.js');
describe("User Service tests", () => {
let userService;
let userRepository;
beforeEach(() => {
userRepository = {
findOne: jest.fn(),
create: jest.fn(),
}
userService = new UserService(userRepository);
});
afterEach(() => {
resetAllMocks();
});
describe("createUser", () => {
it("should be able to create a new user", async () => {
const newUserData = { name: 'User', email: 'user#test.com.br' }
const user = { id: 1, name: 'User', email: 'user#test.com.br' }
userRepository.create.mockResolvedValue(user);
const result = await userService.createUser();
expect(result).toStrictEqual(user);
})
})
})
Note that in the createUser method, there is a call to the getUserByEmail method which is also a method of UserService class, and that is where I got confused.
Should I mock the getUserByEmail method even it is a method of the class I'm testing? If it is not the correct approach, what should I do?
You should almost always prefer not to mock parts of the thing you're supposed to be testing, in this case UserService. To illustrate why, consider these two tests:
Provides a test double implementation for findByEmail on the repo object:
it("throws an error if the user already exists", async () => {
const email = "foo#bar.baz";
const user = { email, name: "Foo Barrington" };
const service = new UserService({
findByEmail: (_email) => Promise.resolve(_email === email ? user : null),
});
await expect(service.createUser(user)).rejects.toThrow("User already exists");
});
Stubs out the service's own getUserByEmail method:
it("throws an error if the user already exists", async () => {
const email = "foo#bar.baz";
const user = { email, name: "Foo Barrington" };
const service = new UserService({});
service.getUserByEmail = (_email) => Promise.resolve(_email === email ? user : null);
await expect(service.createUser(user)).rejects.toThrow("User already exists");
});
For your current implementation, both pass just fine. But let's think about how things might change.
Imagine we need to enrich the user model getUserByEmail provides at some point:
async getUserByEmail(userEmail) {
const user = await this.userRepository.findByEmail(userEmail);
user.moreStuff = await.this.userRepository.getSomething(user.id);
return user;
}
Obviously we don't need this extra data just to know whether or not the user exists, so we factor out the basic user object retrieval:
async getUserByEmail(userEmail) {
const user = await this._getUser(userEmail);
user.moreStuff = await.this.userRepository.getSomething(user.id);
return user;
}
async createUser(userData) {
if (await this._getUser(userData.email)) {
throw new Error("User already exists");
}
return this.userRepository.create(userData);
}
async _getUser(userEmail) {
return this.userRepository.findByEmail(userEmail);
}
If we were using test 1, we wouldn't have to change it at all - we're still consuming findByEmail on the repo, the fact that the internal implementation has changed is opaque to our test. But with test 2, that's now failing even though the code still does the same things. This is a false negative; the functionality works but the test fails.
In fact you could have applied that refactor, extracting _getUser, prior to a new feature making the need so clear; the fact that createUser uses getUserByEmail directly reflects accidental duplication of this.userRepository.findByEmail(email) - they have different reasons to change.
Or imagine we make some change that breaks getUserByEmail. Let's simulate a problem with the enrichment, for example:
async getUserByEmail(userEmail) {
const user = await this.userRepository.findByEmail(userEmail);
throw new Error("lol whoops!");
return user;
}
If we're using test 1, our test for createUser fails too, but that's the correct outcome! The implementation is broken, a user cannot be created. With test 2 we have a false positive; the test passes but the functionality doesn't work.
In this case, you could say that it's better to see that only getUserByEmail is failing because that's where the problem is, but I'd contend that would be pretty confusing when you looked at the code: "createUser calls that method too, but the tests say it's fine...".
You shouldn't mock any of these functions since its creating users and reading data from the database. If you mock them then what's the point of the test. In other words, you wouldn't know if your app is working correctly with the database or not. Anyway, I would mock functions such as the functions that send emails and so on. Don't mock the functions that are the heart of the application. You should have a database for testing and another one for production.

Test AngularFireAuth Wrapper, without using AngularFireAuth mock

I would like to have a AuthWrapper Service that wraps the AngularFireAuth Service. Something like this.
// EDIT: Adding some import statements.
import {TestBed} from '#angular/core/testing';
import { AuthWrapperService } from './auth-wrapper.service';
import {AngularFireModule} from '#angular/fire';
import {AngularFireAuth, AngularFireAuthModule} from '#angular/fire/auth';
import {environment} from '../environments/environment';
#Injectable({
providedIn: 'root'
})
export class AuthWrapper {
constructor(public afAuth: AngularFireAuth) { }
isAuthenticated(): Observable<firebase.User> {
return this.afAuth.user;
}
createUserWithEmailAndPassword(email: string, password: string): Promise<String> {
let authPromise: Promise<firebase.auth.UserCredential> =
this.afAuth.auth.createUserWithEmailAndPassword(email, password);
return authPromise
.then((value) => {
return value.user.email;
})
.catch(function(error) {
return error.message;
});
}
}
I want a wrapper, so that I can test my connection to AngularFireAuth. Other tests mock the AuthWrapper.
( Reason for not mocking AngularFireAuth: Say I mock AngularFireAuth, then I am determining the mock's return values. Then I am saying that I understand what these values would look like. It is not safe to assume this without ever testing these by calling the backend. Say google changes how the results of the real AngularFireAuth's methods, I would then be forced to change the results of each of my AngularFireAuth mocks. Instead it is better to wrap AngularFireAuth in a wrapper, and just change that wrapper's methods to conform to google's changes. )
My tests in Karmine and Jasmine result in an "Async callback was not invoked within 5000ms error." I know the user is signed it because the first expect passes, but how do I get the second expect to work?:
describe('AuthWrapperService', () => {
let fireAuthService: AngularFireAuth;
let password = "dcbA4321!";
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
AngularFireModule.initializeApp(environment.firebase),
AngularFireModule,
AngularFireAuthModule
],
providers: [AngularFireAuth, AuthWrapperService],
declarations: []
})
fireAuthService = TestBed.get(AngularFireAuth);
});
it('should sign in user', async() => {
const email = "anyemail#gmail.com";
let authWrap = new AuthWrapperService(fireAuthService);
let userEmail = await authWrap.createUserWithEmailAndPassword(email, password);
expect(userEmail).toBe("anyemail#gmail.com");
let obs = authWrap.isAuthenticated();
let promise = obs.toPromise();
let user = await promise.then((result) => {return result});
expect(user.email).toBe("anyemail#gmail.com");
});
});
I haven't seen any Angular testing code, where the AngularFireAuth isn't mocked.
These are the tests that worked for me with a time interval of 5000ms. I did test them with "bad#gmail.com" in the expect lines to make sure the expect lines were actually running, and yes, the expect lines are actually passing. I intend to only run the AuthWrapperService tests periodically since they hit the database. I will mock the AuthWrapperService in other tests. Before I run these tests I delete the existing users in the firebase database manually through the firebase console. I do run both of these tests in one go, (inside one describe block).
it('should create new user', async() => {
const annEmail = "ann#gmail.com";
let authWrap = new AuthWrapperService(fireAuthService);
await authWrap.createUserWithEmailAndPassword(annEmail, password).then(
((testEmail) => {
expect(fireAuthService.auth.currentUser.email).toBe(annEmail);
})
)
await fireAuthService.auth.signOut();
});
it('should return error message when user already exists', async() => {
const email = 'ben#gmail.com';
const error = 'already in use';
let authWrap = new AuthWrapperService(fireAuthService);
await authWrap.createUserWithEmailAndPassword(email, password)
.then((value: String) => {
expect(fireAuthService.auth.currentUser.email).toBe(email);
});
await fireAuthService.auth.signOut();
expect(fireAuthService.auth.currentUser).toBeNull();
await authWrap.createUserWithEmailAndPassword(email, password)
.then((value: String) => {
expect(value).toContain('already in use');
});
await fireAuthService.auth.signOut();
});
I decided to use fireAuthService.auth.currentUser.email instead of the AuthWrapperService's isAuthenticated() method to directly test that my AuthWrapperService's createUserWithEmailAndPassword() method creates a user in firebase.
I realize that this is not a unit test, but I have decided that it is better to have an integration test than to mock methods that I don't own.
The following test also worked for me within 5000ms and uses the isAuthenticated() method.
it('should create user cat', async() => {
const catEmail = "cat#gmail.com";
let authWrap = new AuthWrapperService(fireAuthService);
let userEmail = await authWrap.createUserWithEmailAndPassword(catEmail, password);
expect(userEmail).toBe(catEmail);
await authWrap.isAuthenticated().subscribe({
next(user) {
expect(user.email).toBe(catEmail);
}
});
console.log("cat, timeinterval: " + jasmine.DEFAULT_TIMEOUT_INTERVAL);
await fireAuthService.auth.signOut();
});
The following "dan test" resulted in time outs. I tried 29000ms once and it ran out of time. Most runs of this test were 10 seconds. Many times, but not always, this test passes correctly if the user is already a user before the test is run (so it's not really creating a user during the test). One time this test passed correctly when the user had not been created before the test, but I ran this many times without the user existing before the test and it failed. I'm not sure why that would be.
it('should create user dan', done => {
const datEmail = "dan#gmail.com";
let authWrap = new AuthWrapperService(fireAuthService);
authWrap.isAuthenticated().subscribe(
{
next(user){
expect(user.email).toBe(datEmail);
done();
}
}
);
authWrap.createUserWithEmailAndPassword(datEmail, password);
console.log("dan, timeinterval: " + jasmine.DEFAULT_TIMEOUT_INTERVAL);
});
As discussed in comments, its not a good practice to not mock dependencies in case of unit testing. Any cohesive unit should be tested in isolation. And let say if our dependency has number of other dependencies then we cannot provide them all.
When you test the code with the actual dependency, you are not doing unit testing; you are doing integration testing.
Please see this for difference between Integration testing and Unit testing - https://angular.io/guide/testing#use-e2e-end-to-end-to-test-more-than-a-single-unit
You are facing this error because your async spec finishes after the default time out that jasmine has specified, which is 5 seconds. Please find the jasmine documentation on this. URL - https://jasmine.github.io/2.0/introduction.html#section-42
You need to manipulate the jasmine.DEFAULT_TIMEOUT_INTERVAL as per your need, assign it some large value as shown in below example:
describe("long asynchronous specs", function() {
var originalTimeout;
beforeEach(function() {
originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; // 60 second (use value as per need)
});
it("takes a long time", function(done) {
setTimeout(function() {
done();
}, 9000);
});
afterEach(function() {
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
});
});
It still depend on the network as well how fast your promise returns the value. I would suggest you to change the value for jasmine.DEFAULT_TIMEOUT_INTERVAL only in test where you are working with actual dependecies.
I hope this will help.

How can I write a test with for a function which depends on the of return of other?

export function* removeProfile(auth, database, action){
try{
const url = `/users/${action.user.uid}`
const {ref} = database
const result = yield call([database, ref], url)
const {remove} = database.ref()
yield call([result, remove]) //how can i do a test for this case?
} catch({message}) {
yield put(ActionCreator.removeProfileFailure(message))
}
}
I need to do a test of a function that depends on the response of another, how do I do it?
Use mocks.
Your removeProfile function is really easy to be (unit) tested because it has almost zero external dependencies (except for ActionCreator).
So you can pass a mocked version of auth, database and action. A mock is a function with the same signature of the original one but super easy to be instrumented.
There are a lot of ways to test a saga but with a simple saga like yours you can use runSaga that, as the name suggests, runs the whole saga without manually calling saga.next(...).
Here you can find the working example of my test.
Take a look at the test I wrote
const { removeProfile } = require("./remove-profile");
const { runSaga } = require("redux-saga");
test("Mocking the database call", () => {
const removeMock = jest.fn(() => ({
// place here the remove's return value
}));
const databaseMock = {
ref: url => ({
remove: removeMock
})
};
runSaga(
{}, // options
removeProfile, // your saga
// below all the arguments for the saga
null,
databaseMock,
{ user: { uid: "999" } }
);
expect(removeMock).toHaveBeenCalled();
});
Let me know if it's enough clear 😊

Jest - Testing Module Multiple Times in One Test Suite

I have a TypeScript module (should be irrelevant, as I think this affect JS too) and I'm trying to test a module I have. The module imports lots of data from external files and chooses which data should be returned based on the a variable.
I'm attempting to run some tests where I update that variable, re-require the module and run further tests in one file. But my issue is that the require of the file only runs once. I guess it's being cached. Is it possible to tell Jest's require function not to cache or to clear the cache between tests?
Here's some stripped back code of what I'm trying to achieve:
module.ts
import { getLanguage } from "utils/functions";
import * as messagesEn from "resources/translations/en";
import * as messagesFr from "resources/translations/fr";
// Determine the user's default language.
const language: string = getLanguage();
// Set messages based on the language.
let messages: LocaleMessages = messagesEn.default;
if (languageWithoutRegionCode === "fr") {
messages = messagesFr.default;
}
export { messages, language };
test.ts
import "jest";
// Mock the modules
const messagesEn = { "translation1": "English", "translation2": "Words" };
const messagesFr = { "translation1": "Francais", "translation2": "Mots" };
const getLangTest = jest.fn(() => "te-ST");
const getLangEn = jest.fn(() => "en-GB");
const getLangFr = jest.fn(() => "fr-FR");
jest.mock("resources/translations/en", () => ({"default": messagesEn}));
jest.mock("resources/translations/fr", () => ({"default": messagesFr}));
jest.mock("utils/functions", () => ({
getLanguage: getLangTest
})
);
describe("Localisation initialisation", () => {
it("Sets language", () => {
const localisation = require("./localisation");
expect(getLangTest).toHaveBeenCalled();
expect(localisation.language).toEqual("te-ST");
expect(localisation.messages).toEqual(messagesEn);
});
it("Sets english messages", () => {
// THIS GETS THE MODULE FROM THE CACHE
const localisation = require("./localisation");
expect(getLangEn).toHaveBeenCalled();
expect(localisation.language).toEqual("en-GB");
expect(localisation.messages).toEqual(messagesEn);
});
it("Sets french messages", () => {
// THIS GETS THE MODULE FROM THE CACHE
const localisation = require("./localisation");
expect(getLangFr).toHaveBeenCalled();
expect(localisation.language).toEqual("fr-FR");
expect(localisation.messages).toEqual(messagesFr);
});
});
I'm aware the second and third tests won't work anyway as I'd need to update the "utils/functions" mock. The issue is that the code in module.ts only runs once.
So, many thanks to the Jest folks on Discord. It's possible to actually clear the modules from the cache with the jest.resetModules() function.
So my test.ts file will look as follows:
describe("Localisation initialisation", () => {
beforeEach(() => {
jest.resetModules();
});
it("Sets language", () => {
const localisation = require("./localisation");
// Perform the tests
});
it("Sets english messages", () => {
const localisation = require("./localisation");
// Perform the tests
});
it("Sets french messages", () => {
const localisation = require("./localisation");
// Perform the tests
});
});
The beforeEach() call to jest.resetModules() ensures we're re-running the code in the module.

Categories