How to spy on a function called inside class object - javascript

I want to test if this.service.someMethod is called using jasmine spy.
Source file:
// src.ts
import { Service } from 'some-package';
export class Component {
service = new Service();
callMethod() {
this.service.thatMethod();
}
}
Spec file:
// src.spec.ts
import { Component } from './src';
describe('test', () => {
it('calls thatMethod of service', () => {
let comp = new Component();
spyOn(comp.service, 'thatMethod').and.callThrough();
comp.callMethod();
expect(comp.service.thatMethod).toHaveBeenCalled();
});
});
Output:
Failed test: Expected comp.service.thatMethod to have been called.

I would suggest you to refactor your code and take advantage of IoC (inversion of control) pattern. That means that you have to get rid of Service dependency in your Component class and inject it manually, like this:
export class Component {
constructor(service) {
this.service = service;
}
callMethod() {
this.service.thatMethod();
}
}
// Elsewhere in your code
import { Service } from 'some-package';
const component = new Component(new Service());
This approach will allow you to test your components effectively with Service mock:
import { Component } from './src';
describe('test', () => {
it('calls thatMethod of service', () => {
const service = jasmine.createSpyObj('service', ['thatMethod']);
let comp = new Component(service);
comp.callMethod();
expect(service.thatMethod).toHaveBeenCalled();
});
});

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

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

How to spy on an imported function using Sinon?

Let's say we want to test that a specific function is called by another function using Sinon.
fancyModule.js
export const fancyFunc = () => {
console.log('fancyFunc')
}
export default const fancyDefault = () => {
console.log('fancyDefault')
fancyFunc()
}
fancyModule.test.js
import sinon from 'sinon'
import fancyDefault, { fancyFunc } from '../fancyModule'
describe('fancyModule', () => {
it('calls fancyFunc', () => {
const spy = sinon.spy(fancyFunc)
fancyDefault()
expect(spy.called).to.be.true
})
})
When I run this test the actual value is always false. Also, the original function fancyFunc() gets invoked (outputs fancyFunc) instead of being mocked.
You can change the import style, and import your module as an Object like this
import sinon from 'sinon'
import * as myModule from '../fancyModule'
describe('fancyModule', () => {
it('calls fancyFunc', () => {
const spy = sinon.spy(myModule, 'fancyFunc');
myModule.fancyDefault()
expect(spy.called).to.be.true
})
})
You should use https://github.com/speedskater/babel-plugin-rewire/
import sinon from 'sinon'
import fancyDefault, { __RewireAPI__ } from '../fancyModule'
describe('fancyModule', () => {
it('calls fancyFunc', () => {
const spy = sinon.spy()
__RewireAPI__.__Rewire__('fancyFunc', spy)
fancyDefault()
expect(spy.called).to.be.true
})
})
Also, check example: https://github.com/speedskater/babel-plugin-rewire#test-code-2

Mounting a vue instance with avoriaz, but cannot sinon spy on imported function

I have the following component script (some irrelevant bits removed):
import api from '#/lib/api';
export default {
methods: {
upload (formData) {
api.uploadFile(formData).then(response => {
this.$emit('input', response.data);
});
}
}
};
And I have the following test, which uses avoriaz to mount the Vue instance:
import { mount } from 'avoriaz';
import { expect } from 'chai';
import sinon from 'sinon';
import UploadForm from '#/components/UploadForm';
describe('upload', () => {
it('passes form data to api.uploadFile', () => {
const testFormData = { test: 'test' };
const api = {
uploadFile: sinon.spy()
};
const wrapper = mount(UploadForm);
wrapper.vm.api = api;
wrapper.vm.upload(testFormData);
expect(api.uploadFile.called).to.equal(true);
});
});
My sinon spy is never called, and I've tried a couple different variations on the above. What is the best way to spy on an imported function like this? Or am I conceptually approaching this the wrong way?
Problem
You need to stub the api dependency, which is a dependency of the file. This can't be done through the vue instance, since api is not a part of the vue component.
You need to stub the file dependency.
Solution
One method to do this is to use inject-loader.
Steps
Install inject-loader
npm install --save-dev inject-loader
At the top of your file, import UploadForm with inject-loader and vue-loader:
import UploadFormFactory from '!!vue-loader?inject!#/components/UploadForm';
This is a factory function that returns UploadForm with dependencies stubbed.
Now, in your test you need to call UploadFormFactory with the dependency you want stubbed:
const api = {
uploadFile: sinon.spy()
};
const UploadForm = UploadFormFactory({
'#/lib/api': api
})
So your test file will look like:
import { mount } from 'avoriaz';
import { expect } from 'chai';
import sinon from 'sinon';
import UploadFormFactory from '!!vue-loader?inject!#/components/UploadForm';
describe('upload', () => {
it('passes form data to api.uploadFile', () => {
const api = {
uploadFile: sinon.spy()
};
const UploadForm = UploadFormFactory({
'#/lib/api': api
})
const testFormData = { test: 'test' };
const api = {
uploadFile: sinon.spy()
};
const wrapper = mount(UploadForm);
wrapper.vm.upload(testFormData);
expect(api.uploadFile.called).to.equal(true);
});
});
More info
I've written a tutorial with more detail here - https://www.coding123.org/stub-dependencies-vue-unit-tests/
I think Edd's answer is the most encompassing for most scenarios, so I'm marking his as the accepted answer. However, the workaround I came up with was to make the api library a global service (Vue.prototype.$api = api) in my main.js file, and then overwrite the global with a stub before each test.
describe('UploadForm.vue', () => {
let wrapper;
const uploadFile = sinon.stub().returns(Promise.resolve({ data: 0 }));
beforeEach(() => {
wrapper = mount(UploadForm, {
globals: {
$api: { uploadFile }
}
});
});
// ...

React/Flux + Jasmine - Expected spy to have been called fails

I'm not really sure why this happens, I have a very simple Component and test, however, it fails on ✖ should call getState on TestStore. But as getStateFromStores DOES get called, it should be called as well, right? I am clueless atm.
import React, { PropTypes } from 'react'
import TestStore from '../stores/TestStore'
export default class TestComponent extends React.Component {
static propTypes = {
}
static getStateFromStores() {
return TestStore.getState()
}
constructor(props) {
super(props)
this.state = TestComponent.getStateFromStores()
}
render() {
return (
<div>
<img src='' alt=''/>
</div>
)
}
}
Test:
var React = require('react')
var TestUtils = require('react/lib/ReactTestUtils')
var Immutable = require('immutable')
const mockTestStoreData = Immutable.fromJS({
one: {
foo: 'bar'
},
two: {
bar: 'baz'
}
})
describe('TestComponent.jsx', () => {
var TestStore
var TestComponent
var TestComponentEl
var renderedRootElement
var renderedDOMNode
beforeEach(() => {
TestStore = require('../../stores/TestStore')
spyOn(TestStore, 'getState') // .and.returnValue(mockTestStoreData)
TestComponent = require('../TestComponent.jsx')
spyOn(TestComponent, 'getStateFromStores')
TestComponentEl = React.createElement(TestComponent)
renderedRootElement = TestUtils.renderIntoDocument(TestComponentEl)
renderedDOMNode = React.findDOMNode(renderedRootElement)
})
it('should be rendered within a div', () => {
expect(renderedDOMNode.tagName.toUpperCase()).toEqual('DIV')
})
it('should have a static getStateFromStores function', () => {
expect(TestComponent.getStateFromStores).toBeDefined()
})
it('should call getStateFromStores on construction', () => {
expect(TestComponent.getStateFromStores).toHaveBeenCalled()
})
it('should call getState on TestStore', () => {
expect(TestStore.getState).toHaveBeenCalled()
})
})
TestStore.getState() is supposed to be called from TestComponent.getStateFromStores(), but you are spying on TestComponent.getStateFromStores():
...
spyOn(TestComponent, 'getStateFromStores');
...
so the actual implementation is not being invoked during your tests. To spy on a function and also have it called, you could change to:
spyOn(TestComponent, 'getStateFromStores').and.callThrough();
Documentation for callThrough().
That being said, testing whether or not methods get called inside the module you are testing is perhaps too implementation specific. It's better to keep the test more cause-and-effect oriented. So my suggested solution would be to make the following adjustments:
var React = require('react')
var TestUtils = require('react/lib/ReactTestUtils')
var Immutable = require('immutable')
const mockTestStoreData = Immutable.fromJS({
one: {
foo: 'bar'
},
two: {
bar: 'baz'
}
})
describe('TestComponent.jsx', () => {
var TestStore
var TestComponent
var TestComponentEl
var renderedRootElement
var renderedDOMNode
beforeEach(() => {
TestStore = require('../../stores/TestStore')
spyOn(TestStore, 'getState').and.returnValue(mockTestStoreData);
TestComponent = require('../TestComponent.jsx')
// don't spyOn(TestComponent, 'getStateFromStores')
TestComponentEl = React.createElement(TestComponent)
renderedRootElement = TestUtils.renderIntoDocument(TestComponentEl)
renderedDOMNode = React.findDOMNode(renderedRootElement)
})
it('should be rendered within a div', () => {
expect(renderedDOMNode.tagName.toUpperCase()).toEqual('DIV')
})
it('should have a static getStateFromStores function', () => {
expect(TestComponent.getStateFromStores).toBeDefined()
})
// don't
//it('should call getStateFromStores on construction', () => {
// expect(TestComponent.getStateFromStores).toHaveBeenCalled()
//})
// don't
//it('should call getState on TestStore', () => {
// expect(TestStore.getState).toHaveBeenCalled()
//})
// instead
it( 'should get its initial state from TestStore', () => {
expect(TestStore.getState).toHaveBeenCalled(); // I'd say optional
expect( TestComponent.state ).toEqual( mockTestStoreData );
})
})
Now you're free to change the implementation and are testing what is important: the state of TestComponent after it is initialized.

Categories