How to stub exported function from module in cypress? - javascript

I'm not able to see stubbed response for getName method while invoking getUser from Cypress. Is there a way to correct this?
// Service.ts
export const getName = (): string => {
return name;
}
// User.ts
import {getName} from './Service'
export const getUser = (): User => {
const name = getName();
// ... rest of code for User creation
}
// User.cypress.ts
import * as service from './Service'
it('tests user', () => {
cy.stub(service, 'getName').returns('abc');
cy.get('#get-user-id').click();
});

You may need to change the way the function is exported from Service.ts.
Try adding a default export to the module.
// Service.ts
const getName = (): string => {
return name;
}
module.exports {
getName
}
// User.cypress.ts
import service from './Service'
it('tests user', () => {
cy.stub(service, 'getName').returns('abc');
cy.get('#get-user-id').click();
});

Related

How can I mock a class using jest?

How can I mock something to test something like the following codes. I tried to follow this official doc, but still not working for me https://jestjs.io/docs/es6-class-mocks#calling-jestmock-with-the-module-factory-parameter
// somefile.ts
export const myPublish = async (event: any, context: any): Promise<any> => {
const myExportHelper = await ExportHelper.getInstance({
...commonProps,
});
// just some other stuff
// just some other stuff
await myExportHelper.transfer(arg1, arg2);
};
export class ExportHelper {
constructor(
private readonly bucket: string,
private readonly read: AWS.S3,
private readonly write: AWS.S3
) {}
static async getInstance(props: {
param1: string;
}) {
...
...
return new ExportHelper(arg1, arg2, arg3);
};
async transfer(param1, param2) {
...
...
console.log('bla bla bla');
}
}
// testfile.test.ts
import { myPublish, ExportHelper } from '../somefile';
beforeEach(() => {
});
afterEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});
describe('myTest', () => {
it('should run successfully', async () => {
// Arrange
const eventMock = {
Records: [
{
...
}
]
}
jest.mock('../somefile');
const mockActualExportHelper = jest.requireActual('../somefile').ExportHelper;
const mockGetInstanceImpl = () => {};
// this says cannot read property instances of undefined
const mockExportHelper = mockActualExportHelper.mock.instances[0];
mockExportHelper.getInstance.mockImplementation(mockGetInstanceImpl);
mockExportHelper.transfer.mockImplementation(mockGetInstanceImpl);
// Act
await myPublish(eventMock, jasmine.any({}));
// Assert
expect(ExportHelper.getInstance).toBeCalled();
expect(ExportHelper.transfer).toBeCalled(); // also not sure if this is valid to use ExportHelper
});
});
I think what you're looking for is not a mock. If you want to spy what functions are called, you will need to use the spyOn. In jest you can do the following:
jest.spyOn(MyClass, 'myMethod');
And you can also mock the implementation to subtitute the default behavior of a method, a generalist example can be like this:
jest.spyOn(MyClass, 'myMethod').mockImplementation(jest.fn());
With that said, I would rewrite the test to spy the methods from ExportHelper and avoid external calls:
import {ExportHelper, myPublish} from '../app';
beforeEach(() => {
});
afterEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});
describe('myTest', () => {
it('should run successfully', async () => {
// Arrange
const eventMock = {
Records: [
{
// ...
}
]
}
jest.spyOn(ExportHelper, 'getInstance').mockImplementation(jest.fn());
jest.spyOn(ExportHelper, 'transfer').mockImplementation(jest.fn());
// Act
await myPublish('arg1', 'arg2');
// Assert
expect(ExportHelper.getInstance).toBeCalled();
expect(ExportHelper.transfer).toBeCalled();
});
});
I just replaced this piece of code:
jest.mock('../somefile');
const mockActualExportHelper = jest.requireActual('../somefile').ExportHelper;
const mockGetInstanceImpl = () => {};
// this says cannot read property instances of undefined
const mockExportHelper = mockActualExportHelper.mock.instances[0];
mockExportHelper.getInstance.mockImplementation(mockGetInstanceImpl);
mockExportHelper.transfer.mockImplementation(mockGetInstanceImpl);
with
jest.spyOn(ExportHelper, 'getInstance').mockImplementation(jest.fn());
jest.spyOn(ExportHelper, 'transfer').mockImplementation(jest.fn());
Because jest will track down and watch any of these method's calls and then we can use jest's matchers to test if both of them were called. And the mockImplementation will isolate any further calls to be maded.
One thing that I noticed while reproducing your example, is that the transfer method is not being treated as a method when you get the instance from getInstance and therefore, the tests will not pass. But I think this question is not in the scope of the topic. Dunno if just happens to me.

How do I completely mock an imported service class

I am looking to test my next API route which uses the micro framework (similar enough to express when using next-connect).
I have a service:
export class UserService {
constructor(...) {}
async findUser({ email }: Pick<IUserModel, 'email'>) {
...
}
}
My API endpoint:
import { NextApiRequest, NextApiResponse } from 'next';
import nc from 'next-connect';
import { UserService } from './UserService'; // Mock and prevent execution
const userService = new UserService();
export default nc().post(async (req: NextApiRequest, res: NextApiResponse) => {
try {
userService.findUser({ email: 'john.doe#example.com' });
return res.status(200).send({ done: true });
} catch (error: any) {
return res.status(500).end(error.message);
}
});
I would like to completely mock the import and prevent any dependent code from executing.
import { UserService } from './UserService';
For example if there is a console.log() in the UserService constructor it should not be run because the import is a completely mocked import.
Update:
I've attempted to use jest.mock but they didn't seem to actually mock my imports. I've added a console.log in the UserService constructor and it continues to be triggered when using jest.mock.
import signup from './signup';
jest.mock('./UserService');
describe('signup', () => {
it('should complete the happy path', async () => {
const req: IncomingMessage = {} as unknown as IncomingMessage;
const res: ServerResponse = {
end: jest.fn(),
} as unknown as ServerResponse;
const actual = await signup(req, res);
expect(actual).toBe(...);
});
});
If you mean mocking in a jest test then you can just use jest.mock('./userService'); or jest.spyOn(UserService, 'findUser') if you want to mock one method.
If you want a mock for a specific use case, you would create a mock service and conditionally import based on some flag.
Ex:
// UserService.js
export class UserService {
constructor(...) {}
async findUser({ email }: Pick<IUserModel, 'email'>) {
// calls real API
}
}
// UserService.mock.js
export class MockUserService {
constructor(...) {}
async findUser({ email }: Pick<IUserModel, 'email'>) {
// calls fake API or just returns a promise or anything you want
}
}
// Your Endpoint
import { NextApiRequest, NextApiResponse } from 'next';
import nc from 'next-connect';
import { UserService } from './UserService';
import { MockUserService } from './UserService.mock'; // Mock
let userService;
if (someCondition) {
userService = new UserService();
} else {
userService = new MockUserService();
}
...
// The idea is you want to dynamically change what you're importing
const Something = process.env.NODE_ENV === 'development' ?
require('./FakeSomething') : require('./RealSomething');
jest.mock doesn't support mocking dependencies for imports outside of the actual test file (.spec.ts & .test.ts). In my case I have a lot of dependencies in my Next API endpoint that cannot be mocked.
You'll have to find a pattern to inject the dependency into the endpoint and test it in isolation instead. I made use of the Nextjs middleware example here but there are likely other patterns that will work just as well.
// Helper method to wait for a middleware to execute before continuing
// And to throw an error when an error happens in a middleware
function runMiddleware(req: NextApiRequest, res: NextApiResponse, fn: Function) {
return new Promise((resolve, reject) => {
fn(req, res, (result: any) => {
if (result instanceof Error) {
return reject(result)
}
return resolve(result)
})
})
}
// Testable function for DI
export function post(
_userService: UserService,
) {
return async function(req: NextApiRequest, res: NextApiResponse) {
...
}
}
// Endpoint
export default async function(req: NextApiRequest, res: NextApiResponse) {
try {
await runMiddleware(req, res, post(userService));
return res.status(200).send({ done: true });
} catch (error: any) {
return res.status(500).end(error.message);
}
};
// Test usage
describe('signup', () => {
let userService: UserService;
beforeAll(() => {
userService = new UserService({} as unknown as UserRepository);
});
it('should complete the happy path', async () => {
const findSpy = jest
.spyOn(userService, 'find')
.mockImplementation(async () => null);
const req: NextApiRequest = {
body: {
email: 'user#test.com',
password: '12345678',
},
} as unknown as NextApiRequest;
const res: NextApiResponse = {} as unknown as NextApiResponse;
await post(userService)(req, res);
...
});
});

How to mock a node module with jest within a class?

I need to mock the DNS node module in a class but I am unsure how to do so as it is enclosed in the class. Here is a sample of what the class looks like...
import { lookup } from 'dns';
class Foo {
// ...
protected async _bar(IP: string) {
// I want to mock "lookup"
await new Promise<undefined>((resolve, reject) => {
lookup(IP, (err, addr) => {
if (err) reject(new Error('DNS Lookup failed for IP_ADDR ' + IP));
resolve();
});
});
// If dns found then return true
return true;
}
// ...
}
I would like to create a test file foo.spec.ts that contains a test similar to the following:
import { Foo } from './Foo';
describe('Foo', () => {
it('Bar Method returns true on success', () => {
const test = new Foo();
expect(test._bar('192.168.1.1')).resolves.toBeTruthy();
});
});
I am unsure how to mock the lookup call within the class Foo given that the class definition is in a separate file from the test itself.
Any help would be appreciated!
The way you are using lookup won't work since it doesn't return a Promise...
...but you can convert it to a version that does return a Promise by using util.promisify.
The code would end up looking something like this:
import { lookup as originalLookup } from 'dns'; // <= import original lookup...
import { promisify } from 'util';
const lookup = promisify(originalLookup); // <= ...and promisify it
export class Foo {
async _bar(IP: string) {
await lookup(IP).catch(err => { throw new Error('Failed'); });
return true;
}
}
You could then mock lookup in your test using jest.mock like this:
import { Foo } from './Foo';
jest.mock('dns', () => ({
lookup: (hostname, callback) => {
hostname === 'example.com' ? callback() : callback('error');
}
}))
describe('Foo', () => {
it('Bar Method returns true on success', async () => {
const test = new Foo();
await expect(test._bar('example.com')).resolves.toBeTruthy(); // Success!
await expect(test._bar('something else')).rejects.toThrowError('Failed'); // Success!
});
});
Note that the mock needs to be created using jest.mock (and not something like jest.spyOn) since calls to jest.mock get hoisted and run first. The mock needs to be in place before Foo.js is imported since the first thing it does is create and store the promisified lookup.
From jest's tutorial,
jest.mock('./sound-player', () => {
return function() {
return {playSoundFile: () => {}};
};
});
so you could do sth like jest.mock('dns', () => ({ ... }));

How to mock a plugin in Jest

My unit test is not setup properly, meaning that I am not correctly mocking my imported plugin function.
How do I correctly mock my logData function? In the plugin the function returns undefined and that is by choice. I just need to make sure I console.log whatever is passed to it.
The current error I get is "Cannot spy the logData property because it is not a function; undefined given instead"
logData.js - the plugin (just a wrapper around console.log statements)
export function logData (dataToLog) {
const isLoggingData = localStorage.getItem('isLoggingData')
if (isLoggingData) {
console.log(dataToLog)
}
}
export default {
install: function (Vue) {
Vue.prototype.$logData = logData
}
}
logData.spec.js - I mocked localStorage but I need to mock the plugin logData
import Vue from 'vue'
import { createLocalVue } from '#vue/test-utils'
import logData from './logData'
class LocalStorageMock {
constructor () {
this.store = {}
}
getItem (key) {
return this.store[key] || null
}
setItem (key, value) {
this.store[key] = value.toString()
}
removeItem (key) {
delete this.store[key]
}
}
global.localStorage = new LocalStorageMock()
const localVue = createLocalVue()
const dataToLog = 'data to be logged'
localVue.use(logData)
const mockLogDataFunction = jest.spyOn(localVue.prototype, 'logData')
describe('logData plugin', () => {
// PASSES
it('adds a $logData method to the Vue prototype', () => {
console.log(wrapper.vm.$logData)
expect(Vue.prototype.$logData).toBeUndefined()
expect(typeof localVue.prototype.$logData).toBe('function')
})
// Now passes
it('[positive] console.logs data passed to it', () => {
global.localStorage.setItem('isLoggingData', true)
const spy = jest.spyOn(global.console, 'log')
localVue.prototype.$logData('data to be logged')
expect(spy).toHaveBeenCalledWith(dataToLog)
// expect(mockLogDataFunction).toHaveBeenCalled()
// console.log(localVue)
// expect(localVue.prototype.$logData(dataToLog)).toMatch('data to be logged')
})
// PASSES
it('[negative] does not console.log data passed to it', () => {
const spy = jest.spyOn(global.console, 'log')
global.localStorage.removeItem('isLoggingData')
localVue.prototype.$logData(dataToLog)
expect(spy).not.toHaveBeenCalled()
// const spy = jest.spyOn(this.$logData, 'console')
// expect(localVue.prototype.$logData(dataToLog)).toBe(und efined)
// expect(spy).toBe(undefined)
})
})
You are doing things wrong.
jest.spyOn(object, methodName). You need to pass localVue.prototype and 'logData', as arguments, to track if this method was called.
Creates a mock function similar to jest.fn but also tracks calls to
object[methodName]. Returns a Jest mock function.
To check if method was called – expect(spy).toHaveBeenCalled().
So change some lines of your code:
const mockLogDataFunction = jest.spyOn(localVue.prototype, '$logData')
// invoke it and then ensure that that method is really called
localVue.prototype.$logData('foo')
expect(mockLogDataFunction).toHaveBeenCalled()

Why my stub Sinon.Stub is ignored?

I'm wondering why my stub is ignored.
Let say I have a file called myfile.ts which exports two async methods. A and B.
import { sendResult } from '../index'
export async A(): Promise<string[]> {
// making an async api call
const result = await apiCall()
// do sync treatement on result then send it back
...
return value
}
export async B(): Promise<void> {
// Calling A()
const aResult = await A()
// do some sync treatement and call a method from an other module than
// returns a Promise<void>
...
sendResult()
}
I need to unit test my B method and stub A and sendResult
My testFile.ts looks like
import { sandbox } from 'sinon'
import * as AB from './modules/ab'
import * as INDX from './index'
const testSandbox = sandbox.create()
describe('my tests', function () {
beforeEach(() => {
testSandbox.stub(INDX, 'sendResult').resolves()
testSandbox.stub(AB, 'A').resolves([])
})
afterEach(() => {
testSandbox.restore()
})
it('should pass but it is not', async function (done) {
await AB.B()
const sendResultStub = UL.sendResult as SinonStub
assert.calledOnce(sendResultStub)
done()
})
})
I do not understand why sendResult is well stubed but A is not. What do I miss?
Thanks is advances folks!
Bastien

Categories

Resources