How to mock a plugin in Jest - javascript

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()

Related

I have a component that I am trying to test, which utilises a hook that exports a function. How to mock the exported function before the test?

I have a hook called useStartGame which exports just 1 function like this:
const useStartGame = () => {
const startGame = (game) => { //Do something }
return { startGame };
}
Now I have a component which uses this hook, however, I want to overwrite what the function does in my test. I tried adding the following code in my test:
jest.mock('#src/hooks/useStartGame', () => ({
useStartGame: () => {
return { startGame: jest.fn() };
},
}));
However, in my test I get this error:
TypeError: (0 , _useStartGame2.default) is not a function

Jest mock not being used when called from a constant being imported from another file

I have a method which is using a constant defined in another call. This constant in itself makes a service call that I have mocked. When resolving this constant from the other file the mocked function is not being called
//function i am trying in test.ts
export const testableFunction = async() => {
return EXPORTED_CONSTANT
}
//constant being exported from constants.ts
export const EXPORTED_CONSTANT = {
value : service.method()
}
// unit test I have written
it('message trigger for sendInteractiveWhatsapp', async () => {
jest.spyOn(service, 'method').mockImplementation(async () => {
return "some_value";
});
expect(await testableFunction()).toEqual({value: 'some_value'});
});
This test is failing because the mock value being return is undefined. i.e.
{} != {value: 'some_value'}
You can try mocking the constants.ts file at the top of your tests. That way you will be mocking any uses of that module and its contents while your tests run. Following is a sample from jest documentation.
import moduleName, {foo} from '../moduleName';
jest.mock('../moduleName', () => {
return {
__esModule: true,
default: jest.fn(() => 42),
foo: jest.fn(() => 43),
};
});
moduleName(); // Will return 42
foo(); // Will return 43
You can read more about different types of mocks here

Vue-test-utils: How do I mock the return of an action in VueX?

I'm writing a test for a Vue component which dispatches to a module store to perform an action and use the result from it.
The action makes a call to our API so I don't want to run the test with that action, but instead mock it and return some dummy data to see that the rest of the method flow works.
So in my test I add a mock store, with a mocked action which just returns hardcoded data, with the intent to see that the component method getData() sets the response of the action to the data.
This doesn't seem to work however and instead it seems as if the real action is called. How do I set it up so that I can avoid calling the real actions and instead use the ones I create for the tests?
Component method, simplified:
methods: {
async getData() {
const response = this.$store.dispatch("global/getDataFromAPI")
if (!response) return
this.$data.data = {...response.data}
}
}
Test code, simplified:
describe('Component.vue', () => {
let localVue;
let vuetify;
let wrapper;
let store;
beforeEach(() => {
localVue = createLocalVue();
localVue.use(Vuex)
vuetify = new Vuetify();
let globalActions = {
getDataFromAPI: async () => {
return {
status: 200,
data: {
information1: "ABC",
information2: 123,
}
}
}
}
store = new Vuex.Store({
modules: {
global: {
actions: globalActions,
namespaced: false
},
}
})
wrapper = mount(Component, {
localVue,
vuetify,
attachTo: div,
mocks: {
$t: () => { },
$store: store,
},
});
});
it('Data is set correctly', async () => {
await wrapper.vm.getData();
const dataInformation1 = wrapper.vm.$data.data.information1;
expect(dataInformation1).toBe("ABC")
});
First, if you want to mock the Vuex Store you don't need to call localVue.use(Vuex). You should call localVue.use(Vuex) only if you are going to use real Vuex Store in test. And if you are going to you must pass store object along with localVue and another arguments, not in mocks property.
Second, to mock your action you can mock store's dispatch method like this:
mocks: {
$store: {
dispatch: () => { dummyData: 'dummyData' }
}
}

How to mock a function returned by a mocked function

I am trying to write mock a test for the following function call
const contract = await new web3.eth.Contract();
tx.data = await contract.methods
.setup(
[juryAddress, buyerAddress, sellerAddress],
threshold,
to,
txData,
fallbackHandler,
paymentToken,
0,
sellerAddress
)
.encodeABI();
I am trying to mock this as a function contract.methods.setup() that returns a function object encodeABI() which then returns a dummy value {}.
The mock I am trying looks like this, although it is not working
const encodeABI = jest.fn()
encodeABI.mockReturnValue({})
const contract = {
methods: {
setup: jest.fn(),
}
}
eth.Contract.mockResolvedValue(contract)
contract.methods.setup.mockResolvedValue(encodeABI)
expect(encodeABI).toBeCalled()
expect(encodeABI).toBeCalled() is not being called as I expect
Your production code awaits encodeABI(), not setup(). It expects setup() to return an object with an encodeABI() function, not a Promise.
I would recommend making the following changes
const encodeABI = jest.fn(async () => ({})) // return a Promise here
const contract = {
methods: {
setup: jest.fn(() => ({ encodeABI })) // return an object with encodeABI
}
}

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', () => ({ ... }));

Categories

Resources