Running a method when a module is called? - javascript

Consider this:
export default () => ({
init() {
alert('hello');
},
});
Which is called via:
import Test from './Test';
let test = Test();
test.init();
Is there a way to just have a method run without having to call it? For example, could I just do:
import Test from './Test';
let test = Test();
And have something default run?
-----EDIT
I wish to have a module that can be called like:
import Test from './Test';
let test = Test();
But the module should also have various methods in it. Basically - is there some sort of constructor?

export default () => {
console.log('this runs when `Test()` is called')
return {
init() {
console.log('this runs when `test.init()` is called')
}
}
}

Related

How to test if an imported ES6 function is called on useEffect inside the component?

I'm trying to create a simple test in Jest that should check if an imported function is called inside useEffect in the component's lifecycle.
index.js
import { myFunc } from './myFunctions';
const myComponent = () => {
useEffect(() => {
myFunc()
}, [])
return ()
}
export default MyComponent
And now I want to create a test index.test.js file to test if that myFunc was called one time. I've got a special function that renders the mocked component (it's called prepare()).
index.test.js
import * as myFunctions from './myFunctions';
describe('when the component renders', () => {
it('the function should be called once', () => {
const spy = jest.spyOn(myFunctions, 'myFunc');
prepare(); // Function rendering myComponent
expect(spy).toHaveBeenCalledTimes(1);
});
myFunctions.js
export const myFunc = () => {
doSomething()
}
...
My error
expect(jest.fn()).toHaveBeenCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 0

Mocking exported exports in Jest

I am having a problem whereby if I export * from submodule (using ES6 module syntax and babel) I am unable to mock the submodules functions using Jest from the entry point. I wondered if anyone out there could help...
For example given this structure:
+ __tests__
| |- index.js
+ greeter
| |- index.js
| |- submodule.js
|- index.js
And this code:
index.js
import { sayHello } from "./greeter";
export const greet = (name) => sayHello(name);
greeter/index.js
export * from "./submodule.js";
greeter/submodule.js
export const sayHello = (name) => console.log(`Hello, ${name}`);
__tests__/index.js
import { greet } from "../index";
import * as greeter from "../greeter";
describe("greet", () => {
it("Should delegate the call to greeter.sayHello", () => {
const name = "John";
greet(name);
});
});
This all works fine and when the test runs it passes. Hello, John is printed to the console as expected. The advantage that make this worth it to me is that index.js is completely unaware of the structure of the greeter module, so i can restructure and refactor that code without worrying about my consumers.
The Rub comes when I try and mock out greeter.sayHello...
__tests__/index.js
import { greet } from "../index.js";
import * as greeter from "../greeter";
greeter.sayHello = jest.fn();
describe("greet", () => {
it("Should delegate the call to greeter.sayHello", () => {
const name = "John";
greet(name);
expect(greeter.sayHello).toHaveBeenCalledWith(name);
});
});
Now instead of the test passing as expected - I get an error:
Test suite failed to run
TypeError: Cannot set property sayHello of [object Object] which only has a getter
...(stack trace)
Changing the greeter import in __tests__/index.js to:
import * as greeter from "../greeter/submodule";
Makes the test pass but puts the coupling back in my test code.
Is there another way?
In order to mock a imported method on the file you want to test you need make sure the mock ran before you import your file(index.js), like this:
// import { greet } from "../index.js"; =====> Import on each test case(it)
// import * as greeter from "../greeter"; =====> Mock on each test case(it)
// greeter.sayHello = jest.fn(); =====> Would be not good to do this, it will mess with the entire import and this jest.fn will be share across your tests making it gives you false positives.
describe("greet", () => {
it("Should delegate the call to greeter.sayHello", () => {
const name = "John";
jest.mock('../greeter', () => ({ sayHello: jest.fn() })); // here you mock the import, remember it needs to be before you require your index.js
const greet = require('../index.js').greet; // require after the mock
greet(name);
expect(greeter.sayHello).toHaveBeenCalledWith(name);
});
});

how to mock module when using jest

I'm using Jest for unit test in a vuejs2 project but got stuck in mocking howler.js, a library imported in my component.
Suppose I have a component named Player.vue
<template>
<div class="player">
<button class="player-button" #click="play">Player</button>
</div>
</template>
<script>
import { Howl } from 'howler';
export default {
name: 'audioplayer',
methods: {
play() {
console.log('player button clicked');
new Howl({
src: [ 'whatever.wav' ],
}).play();
}
}
}
</script>
then I have its test file named Player.spec.js. Test code was written based on the answer here, but the test failed since called wasn't set as true. It seems that mocked constructor won't be called when running this test.
import Player from './Player';
import Vue from 'vue';
describe('Player', () => {
let called = false;
jest.mock('howler', () => ({
Howl({ src }) {
this.play = () => {
called = true;
console.log(`playing ${src[0]} now`);
};
},
}));
test('should work', () => {
const Constructor = Vue.extend(Player);
const vm = new Constructor().$mount();
vm.$el.querySelector('.player-button').click();
expect(called).toBeTruthy(); // => will fail
})
})
Though I'm using Vuejs here, I considered it as a more general question related to the usage of Jest's mock API, but I'm not able to get further.
The SO you linked to only works for react components. Here is a way to mock the module with a spy on the play function that can be tested with toBeHaveCalled
//import the mocked module
import { Howl } from 'howler';
// mock the module so it returns an object with the spy
jest.mock('howler', () => ({Howl: jest.fn()}));
const HowlMock ={play: jest.fn()}
// set the actual implementation of the spy so it returns the object with the play function
Howl.mockImplementation(()=> HowlMock)
describe('Player', () => {
test('should work', () => {
const Constructor = Vue.extend(Player);
const vm = new Constructor().$mount();
vm.$el.querySelector('.player-button').click();
expect(Howl).toBeHaveCalledWith({src:[ 'whatever.wav' ]})
expect(HowlMock.play).toBeHaveCalled()
})
})

react-native imported(?) function arguments is so weired

My react native project skeleton here
-app
--component
--LoginScreen.js
--container
--styles.js
-index.ios.js
-index.android.js
and styles.js....
...
export const colors = {
'green' : '#######'
....
}
export const test = () => {
console.log(arguments);
}
...
and LoginScreen.js
import { test } from '../styles';
export default class LoginScreen {
....
constructor () {
test();
}
....
}
so watch chrome debug console...
Arguments[5]
0:DedicatedWorkerGlobalScope
1:_require(moduleId)
2:Object
3:Object
4:null
callee:(global, require, module, exports)
length:5
Symbol(Symbol.iterator):values()
__proto__:Object
what is this?
imported function always return arguments[5]
I don't know why return that arguments.
I think that this related import? function.
Let me know please
Arrow functions do not bind their arguments. If you want to use variable number of arguments in React Native, you can use rest parameter syntax ... to get an array of arguments.
export const test = (...args) => {
console.log(args);
}
The arguments object should work with named function expressions:
export function test() {
console.log(arguments);
}

How can I mock an ES6 module import using Jest?

I want to test that one of my ES6 modules calls another ES6 module in a particular way. With Jasmine this is super easy --
The application code:
// myModule.js
import dependency from './dependency';
export default (x) => {
dependency.doSomething(x * 2);
}
And the test code:
//myModule-test.js
import myModule from '../myModule';
import dependency from '../dependency';
describe('myModule', () => {
it('calls the dependency with double the input', () => {
spyOn(dependency, 'doSomething');
myModule(2);
expect(dependency.doSomething).toHaveBeenCalledWith(4);
});
});
What's the equivalent with Jest? I feel like this is such a simple thing to want to do, but I've been tearing my hair out trying to figure it out.
The closest I've come is by replacing the imports with requires, and moving them inside the tests/functions. Neither of which are things I want to do.
// myModule.js
export default (x) => {
const dependency = require('./dependency'); // Yuck
dependency.doSomething(x * 2);
}
//myModule-test.js
describe('myModule', () => {
it('calls the dependency with double the input', () => {
jest.mock('../dependency');
myModule(2);
const dependency = require('../dependency'); // Also yuck
expect(dependency.doSomething).toBeCalledWith(4);
});
});
For bonus points, I'd love to make the whole thing work when the function inside dependency.js is a default export. However, I know that spying on default exports doesn't work in Jasmine (or at least I could never get it to work), so I'm not holding out hope that it's possible in Jest either.
Edit: Several years have passed and this isn't really the right way to do this any more (and probably never was, my bad).
Mutating an imported module is nasty and can lead to side effects like tests that pass or fail depending on execution order.
I'm leaving this answer in its original form for historical purposes, but you should really use jest.spyOn or jest.mock. Refer to the jest docs or the other answers on this page for details.
Original answer follows:
I've been able to solve this by using a hack involving import *. It even works for both named and default exports!
For a named export:
// dependency.js
export const doSomething = (y) => console.log(y)
// myModule.js
import { doSomething } from './dependency';
export default (x) => {
doSomething(x * 2);
}
// myModule-test.js
import myModule from '../myModule';
import * as dependency from '../dependency';
describe('myModule', () => {
it('calls the dependency with double the input', () => {
dependency.doSomething = jest.fn(); // Mutate the named export
myModule(2);
expect(dependency.doSomething).toBeCalledWith(4);
});
});
Or for a default export:
// dependency.js
export default (y) => console.log(y)
// myModule.js
import dependency from './dependency'; // Note lack of curlies
export default (x) => {
dependency(x * 2);
}
// myModule-test.js
import myModule from '../myModule';
import * as dependency from '../dependency';
describe('myModule', () => {
it('calls the dependency with double the input', () => {
dependency.default = jest.fn(); // Mutate the default export
myModule(2);
expect(dependency.default).toBeCalledWith(4); // Assert against the default
});
});
You have to mock the module and set the spy by yourself:
import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency', () => ({
doSomething: jest.fn()
}))
describe('myModule', () => {
it('calls the dependency with double the input', () => {
myModule(2);
expect(dependency.doSomething).toBeCalledWith(4);
});
});
Fast forwarding to 2020, I found this blog post to be the solution: Jest mock default and named export
Using only ES6 module syntax:
// esModule.js
export default 'defaultExport';
export const namedExport = () => {};
// esModule.test.js
jest.mock('./esModule', () => ({
__esModule: true, // this property makes it work
default: 'mockedDefaultExport',
namedExport: jest.fn(),
}));
import defaultExport, { namedExport } from './esModule';
defaultExport; // 'mockedDefaultExport'
namedExport; // mock function
Also one thing you need to know (which took me a while to figure out) is that you can't call jest.mock() inside the test; you must call it at the top level of the module. However, you can call mockImplementation() inside individual tests if you want to set up different mocks for different tests.
To mock an ES6 dependency module default export using Jest:
import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency');
// If necessary, you can place a mock implementation like this:
dependency.mockImplementation(() => 42);
describe('myModule', () => {
it('calls the dependency once with double the input', () => {
myModule(2);
expect(dependency).toHaveBeenCalledTimes(1);
expect(dependency).toHaveBeenCalledWith(4);
});
});
The other options didn't work for my case.
Adding more to Andreas' answer. I had the same problem with ES6 code, but I did not want to mutate the imports. That looked hacky. So I did this:
import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency');
describe('myModule', () => {
it('calls the dependency with double the input', () => {
myModule(2);
});
});
And added file dependency.js in the " __ mocks __" folder parallel to file dependency.js. This worked for me. Also, this gave me the option to return suitable data from the mock implementation. Make sure you give the correct path to the module you want to mock.
The question is already answered, but you can resolve it like this:
File dependency.js
const doSomething = (x) => x
export default doSomething;
File myModule.js
import doSomething from "./dependency";
export default (x) => doSomething(x * 2);
File myModule.spec.js
jest.mock('../dependency');
import doSomething from "../dependency";
import myModule from "../myModule";
describe('myModule', () => {
it('calls the dependency with double the input', () => {
doSomething.mockImplementation((x) => x * 10)
myModule(2);
expect(doSomething).toHaveBeenCalledWith(4);
console.log(myModule(2)) // 40
});
});
None of the answers here seemed to work for me (the original function was always being imported rather than the mock), and it seems that ESM support in Jest is still work in progress.
After discovering this comment, I found out that jest.mock() does not actually work with regular imports, because the imports are always run before the mock (this is now also officially documented). Because of this, I am importing my dependencies using await import(). This even works with a top-level await, so I just have to adapt my imports:
import { describe, expect, it, jest } from '#jest/globals';
jest.unstable_mockModule('../dependency', () => ({
doSomething: jest.fn()
}));
const myModule = await import('../myModule');
const dependency = await import('../dependency');
describe('myModule', async () => {
it('calls the dependency with double the input', () => {
myModule(2);
expect(dependency.doSomething).toBeCalledWith(4);
});
});
I solved this another way. Let's say you have your dependency.js
export const myFunction = () => { }
I create a depdency.mock.js file besides it with the following content:
export const mockFunction = jest.fn();
jest.mock('dependency.js', () => ({ myFunction: mockFunction }));
And in the test, before I import the file that has the dependency, I use:
import { mockFunction } from 'dependency.mock'
import functionThatCallsDep from './tested-code'
it('my test', () => {
mockFunction.returnValue(false);
functionThatCallsDep();
expect(mockFunction).toHaveBeenCalled();
})
I tried all the solutions and none worked or were showing lots of TS errors.
This is how I solved it:
format.ts file:
import camelcaseKeys from 'camelcase-keys'
import parse from 'xml-parser'
class Format {
parseXml (xml: string) {
return camelcaseKeys(parse(xml), {
deep: true,
})
}
}
const format = new Format()
export { format }
format.test.ts file:
import format from './format'
import camelcaseKeys from 'camelcase-keys'
import parse from 'xml-parser'
jest.mock('xml-parser', () => jest.fn().mockReturnValue('parsed'))
jest.mock('camelcase-keys', () => jest.fn().mockReturnValue('camel cased'))
describe('parseXml', () => {
test('functions called', () => {
const result = format.parseXml('XML')
expect(parse).toHaveBeenCalledWith('XML')
expect(camelcaseKeys).toHaveBeenCalledWith('parsed', { deep: true })
expect(result).toBe('camel cased')
})
})
I made some modifications on #cam-jackson original answer and side effects has gone. I used lodash library to deep clone the object under test and then made any modification I want on that object. But be ware that cloning heavy objects can have negative impact on test performance and test speed.
objectUndertest.js
const objectUnderTest = {};
export default objectUnderTest;
objectUnderTest.myFunctionUnterTest = () => {
return "this is original function";
};
objectUndertest.test.js
import _ from "lodash";
import objectUndertest from "./objectUndertest.js";
describe("objectUndertest", () => {
let mockObject = objectUndertest;
beforeEach(() => {
mockObject = _.cloneDeep(objectUndertest);
});
test("test function", () => {
mockObject.myFunctionUnterTest = () => {
return "this is mocked function.";
};
expect(mockObject.myFunctionUnterTest()).toBe("this is mocked function.");
});
});

Categories