EmberJS: Unit test class-based helpers - javascript

I have several class-based helpers in my app, mostly so I can include the i18n service. Everything works nicely, however, I can't figure out a way to test them.
The auto generated test is not working, as it expects a function to be exported and complains, that undefined is not a constructor:
module('Unit | Helper | format number');
// Replace this with your real tests.
test('it works', function(assert) {
let result = formatNumber([42]);
assert.ok(result);
});
So I tried using moduleFor as it already worked for testing mixins, but also failed:
moduleFor('helper:format-number', 'Unit | Helper | format number', {
needs: ['service:i18n']
});
test('it works', function(assert) {
let result = FormatNumber.compute(42);
assert.ok(result);
});
I tried all the different versions of instantiating the helper object and calling compute on it but nothing worked. In the end it either always returned null or failed with an undefined error.
Did anyone manage to succeed where I failed?

The issue in your example is that you're trying to call compute as a static method on the class itself, when what you really need is an instance of the class.
Here's an example for a Class-based helper that I'm testing. Note; it uses mocha, not qunit, but all of the concepts are the same.
import { expect } from 'chai';
import { beforeEach, describe, it } from 'mocha';
import ShareImage from 'my-app/helpers/share-image';
describe('ShareImageHelper', function() {
beforeEach(function() {
this.helperClass = ShareImage.create({
location: {
hostWithProtocolAndPort: ''
}
});
this.helper = this.helperClass.compute.bind(this.helperClass);
});
it('calculates the `assetPath` correctly', function() {
const assetPath = this.helperClass.get('assetPath');
expect(assetPath).to.equal('/assets/images/social/');
});
it('calculates the path to an image correctly', function() {
const value = this.helper(['foo', 'bar', 'baz']);
expect(value).to.equal('/assets/images/social/foo/bar/baz.png');
});
});
The key here is that on each test run (in the beforeEach callback) I create a new instance of the helper, and then create a function that my tests can call the same way the template helper will be called (this.helper). This allows the tests to look as much like the real code as possible, while still giving me the ability to modify the helper class during tests, which you can also see in the beforeEach callback.

Related

Test method called in mounted hook Vue class component typescript

I am trying to test that a method is called on mount of Vue component. Fairly new to Vue and Typescript.
export default class App extends Vue {
mounted () {
this.deviceId()
this.ipAddress()
this.channel()
this.show()
this.campaign()
this.adUnit()
}
this approach works but I get a warning:
it('mounted methods are called', async () => {
const deviceId = jest.fn()
wrapper = shallowMount(App, {
methods: {
deviceId
}
})
expect(deviceId).toHaveBeenCalled()
})
The error:
console.error node_modules/#vue/test-utils/dist/vue-test-utils.js:1735
[vue-test-utils]: overwriting methods via the `methods` property is deprecated and will be removed in the next major version. There is no clear migration path for the `methods` property - Vue does not support arbitrarily replacement of methods, nor should VTU. To stub a complex method extract it from the component and test it in isolation. Otherwise, the suggestion is to rethink those tests.
I have tried using jest spyOn, but I cannot find a way to access the method;
const spy = jest.spyOn(App.prototype, 'methodName')
wrapper = shallowMount(App)
expect(spy).toHaveBeenCalled()
Gives the following error:
Cannot spy the deviceId property because it is not a function; undefined given instead
The following also doesn't work:
const spy = jest.spyOn(App.methods, 'methodName')
Error:
Property 'methods' does not exist on type 'VueConstructor<Vue>'.ts(2339)
And the following:
const spy = jest.spyOn(App.prototype.methods, 'deviceId')
Error:
Cannot spyOn on a primitive value; undefined given
I have read in places I may need to define an interface for the component but I am not sure how this works with defining functions inside or if it is necessary?
I've been facing the same issue for a few days, but I've found the way of pointing to the correct method when calling jest.spyOn().
It's a bit tricky but you'll find the methods of your class like this:
const spy = jest.spyOn(App.prototype.constructor.options.methods, 'deviceId');
Note that (even if it might seem obvious, just in case) you'll need to do this before wrapping your component, i.e. like this:
const spy = jest.spyOn(App.prototype.constructor.options.methods, 'deviceId');
wrapper = mount(App, { /* Your options go here */ });
By the way, you don't need to define methods property inside options.
Define your method under the property methods. Only then you can access them from the class.
export default class App extends Vue {
methods: {
deviceId(){
console.log("do your stuff")
}
}
}
See here for more examples for the usage of methods

Cannot mock external node module

I am trying to mock an external module (jwt_decode for those interested), and I have seen many examples of how to mock external an node module using Jest both for all tests in a test suite, as well as on a per-test basis.
I have been able to mock the dependency so that it mocks the return value for all tests in the suite, although the default function is all that i'm really concerned with.
import jwt_decode from 'jwt-decode';
jest.mock('jwt-decode', () => jest.fn().mockReturnValue({
exp: 12345,
somethingElse: 'test_value'
}));
This works well, except I would like to test a scenario where the returned token has expired, so that I can verify that certain Redux actions were dispatched when this situation arises.
import jwt_decode from 'jwt-decode';
const decoded = jwt_decode(localStorage.jwtToken);
// set user info and isAuthenticated
store.dispatch(setCurrentUser(decoded));
// this is the scenario I am looking to test
const currentTime = Date.now() / 1000;
if (decoded.exp < currentTime) {
store.dispatch(logoutUser());
store.dispatch(clearCurrentProfile());
window.location.href = '/login';
}
I would like to modify the returned mock for an individual test so that I can ensure that the 'if' statement shown equates to false, and other parts of the code base are executed.
How can one go about this?
Some of the examples I have tried and failed with so far include:
test('some test that will use a different mock' () => {
// re-assign value of the imported module using the its given alias
jwt_decode = jest.fn().mockImplementation(() => {
return {
exp: 'something_different 9999999999',
somethingElse: 'I_changed_too'
};
});
});
As well as
jwt_decode.default = jest.fn().mockImplementation(() => {
return {
exp: 'something_different 9999999999',
somethingElse: 'I_changed_too'
};
});
And also jest.spyOn(), as seen in this SO question, as well as A Jar of Clay's answers on the same question, which proposes the following:
import { funcToMock } from './somewhere';
jest.mock('./somewhere');
beforeEach(() => {
funcToMock.mockImplementation(() => { /* default implementation */ });
});
test('case that needs a different implementation of funcToMock', () => {
funcToMock.mockImplementation(() => { /* implementation specific to this test */ });
// ...
});
I also found a suggestion for creating a util which changes the global localStorage on a test by test basis, but I would rather not use a real jsonwebtoken, or have to worry about storing sign-in credentials.
What I keep ending up with is that jwt_decode is not updated when running the test that should have the different mock value returned, or what's more common is that I get an error saying that ".default is not a function".
If you have suggestions, I would be very grateful.
Assuming I understand your ultimate goal, how about this approach:
In your project directory, at the same level as node_modules, create a directory called "__mocks__" and in that directory, put a file called "jwt-decode.js" -- with this in place there is no need to explicitly mock jwt-decode in your test module, as it will always be mocked automatically.
Put the following code in your __mocks__/jst_decode.js file:
export default token => token;
Thus, when the code that you're testing calls jwt_decode(something), the return value will be exactly what was passed in.
Now you can write unit tests that test the behavior of your module given various valid or invalid values in the token; just mock the token value in your module and your mocked implementation of jwt_decode() will simply pass it through.

Stubbing function with jest

I understand that this is the basic function of jest and I should be able to figure it out with the docs and other online resources, but somehow I cannot, so I apologise in advance if this is trivial.
I'm trying to test a function in Javascript that performs a few operations using other modules, as well as localStorage, and I would like to stub out the other modules and the call to localStorage. The documentation that I found for Jest seemed far too simplistic for me to adapt to my use case, like declaring a mock and then calling it inside the test - this doesn't happen in my case, as the function I want to mock is being called internally by my function, I'm not passing it in as a dependency. Let me give some code to explain: file name is dataInLocalStorage.js
import serialize from './serialize'; // simple module that serialises data
import deserialize from './deserialize'; // simple module that deserialises data
import findObject from './findObject'; // find an object in the deserialised data
const addDataToLocalStorage = (data) => {
const dataStored = deserialize(localStorage.getItem('data')); // fetch data from localStorage
const isStored = !!findObject(dataStored, data); // check whether the data I want to store is already there
if (isStored) { return null; } // if data is already stored, skip
const serializedData = serialize(data); // serialise data to be stored
return localStorage.setItem('data', serializedData); // store serialised data in localStorage
};
export { addDataToLocalStorage };
The purpose os this module is just to store data in localStorage in a serialised way, but in an additive way, so that adding data doesn't remove previously stored data, and no duplicates are added either.
Anyway, my test file looks like this: file name is dataInLocalStorage.test.js
import { addDataToLocalStorage } from '../dataInLocalStorage';
describe('addDataToLocalStorage', () => {
const deserialize = jest.fn();
beforeAll(() => {
localStorage.removeItem('data');
});
const data = {
name: 'johnny'
};
addDataToLocalStorage(data);
it('adds the data to local storage', () => {
expect(deserialize).toHaveBeenCalled();
});
});
Here is the rather unsurprising error for this attempt.
expect(jest.fn()).toHaveBeenCalled()
Expected mock function to have been called, but it was not called.
17 |
18 | it('adds the data to local storage', () => {
> 19 | expect(deserialize).toHaveBeenCalled();
| ^
20 | });
21 | });
On top of this I tried importing the deserialize function here in the test file and adding a jest.mock on that, which didn't work either.
Note that this isn't my code 100%, I have modified it for simplicity in order to make it easier to read for you, sorry if there are some slight mismatches, I tried my best to be as diligent as possible while converting it.
If you know what you're looking at, you'll see that this is obviously not working. Using other (more useful) expectations, the test was passing, but adding some console logs in the deserialize file showed that it's still running, when the idea is that I would like to mock it and provide my own return value.
Side note: I came from Ruby on Rails where mocking with RSpec is pretty simple, and I was hoping it would be just as simple with Jest. It likely is, but I can't wrap my head around it, as it doesn't seem possible to make a direct reference to the function/module I want to mock. In RSpec, doing allow(MySerializer).to receive(:call).and_return(...) would do the trick and I wouldn't have to worry about that module being called during the test.
When you set the value of deserialize to a jest mock, you are changing the variable value, not setting a reference that your code is using. To keep it a reference it needs to be a value in an object.
To import an object you can use import * as deserialize from "./deserialize";.
Then you can set the mock on the reference with deserialize.default = jest.fn().
https://codesandbox.io/s/88wlzp6q88
import { useIt } from "./use-default-export";
import * as myfunc from "./default-export-function";
test("use-default-export-function", () => {
expect(useIt()).toEqual("real");
});
test("use-default-export-function with mock", () => {
myfunc.default = jest.fn(() => "unreal");
expect(useIt()).toEqual("unreal");
});
in your test it'll be..
import { addDataToLocalStorage } from '../dataInLocalStorage';
import * as deserialize from './deserialize';
...
deserialize.default = jest.fn();
alternate TS compat version..
(which is actually cleaner all round..)
import { useIt } from "./use-default-export";
import myfunc from "./default-export-function";
jest.mock("./default-export-function", () => jest.fn());
test("use-default-export-function with mock", () => {
useIt();
expect(myfunc).toHaveBeenCalled();
});
return/resolve different values per test
(need to cast to jest.Mock to be able to use jest.fn() functions)
test("use-default-export-function with mock", () => {
const aFunc = myfunc as jest.Mock;
aFunc.mockResolvedValue("bar");
useIt();
expect(useIt()).resolves.toEqual("bar");
});
test("use-default-export-function with mock 2", () => {
const aFunc = myfunc as jest.Mock;
aFunc.mockReturnValue("foo");
useIt();
expect(useIt()).toEqual("foo");
});

Why does mutating a module update the reference if calling that module from another module, but not if calling from itself?

This question pertains to testing javascript and mocking functions.
Say I have a module that looks like this:
export function alpha(n) {
return `${n}${beta(n)}${n}`;
}
export function beta(n) {
return new Array(n).fill(0).map(() => ".").join("");
}
Then I can't test it the following way:
import * as indexModule from "./index";
//Not what we want to do, because we want to mock the functionality of beta
describe("alpha, large test", () => {
it("alpha(1) returns '1.1'", () => {
expect(indexModule.alpha(1)).toEqual("1.1"); //PASS
});
it("alpha(3) returns '3...3'", () => {
expect(indexModule.alpha(3)).toEqual("3...3"); //PASS
});
});
//Simple atomic test
describe("beta", () => {
it("beta(3) returns '...'", () => {
expect(indexModule.beta(3)).toEqual("..."); //FAIL: received: 'x'
});
});
//Here we are trying to mutate the beta function to mock its functionality
describe("alpha", () => {
indexModule.beta = (n) => "x";
it("works", () => {
expect(indexModule.alpha(3)).toEqual("3x3"); //FAIL, recieved: '3...3'
});
});
However, if split the module into two:
alpha.js
import { beta } from "./beta";
export function alpha(n) {
return `${n}${beta(n)}${n}`;
}
beta.js
export function beta(n) {
return new Array(n).fill(0).map(() => ".").join("");
}
Then I can mutate the beta module, and alpha knows about it:
import { alpha } from "./alpha";
import * as betaModule from "./beta";
describe("alpha", () => {
betaModule.beta = (n) => "x";
it("works", () => {
expect(alpha(3)).toEqual("3x3"); //PASS
});
});
Why is this the case? I'm looking for a technically specific answer.
I have a Github branch with this code here, see the mutateModule and singleFunctionPerModuleAndMutate folders.
As an additional question - in this example I am mutating the module by directly reassigning properties. Am I right in understanding that using jest mock functionality is going to be essentially doing the same thing?
ie. If the reason that the first example doesn't work but the second doesn't is due to the mutation, then it necceserily means that using the jest module mocking functions is similarly not going to work.
As far as I know - there is not way to mock a single function in a module, while testing that module, as this jest github issues talks about. What I'm wanting to know - is why this is.
Why does mutating a module update the reference if calling that module from another module, but not if calling from itself?
"In ES6, imports are live read-only views on exported values".
When you import an ES6 module you essentially get a live view of what is exported by that module.
The live view can be mutated, and any code that imports a live view of the module exports will see the mutation.
That is why your test works when alpha and beta are in two different modules. The test modifies the live view of the beta module, and since the alpha module uses the live view of the beta module, it automatically uses the mocked function instead of the original.
On the other hand, in the code above alpha and beta are in the same module and alpha calls beta directly. alpha does not use the live view of the module so when the test modifies the live view of the module it has no effect.
As an additional question - in this example I am mutating the module by directly reassigning properties. Am I right in understanding that using jest mock functionality is going to be essentially doing the same thing?
There are a few ways to mock things using Jest.
One of the ways is using jest.spyOn which accepts an object and a method name and replaces the method on the object with a spy that calls the original method.
A common way to use jest.spyOn is to pass it the live view of an ES6 module as the object which mutates the live view of the module.
So yes, mocking by passing the live view of an ES6 module to something like jest.spyOn (or spyOn from Jasmine, or sinon.spy from Sinon, etc.) mutates the live view of the module in essentially the same way as directly mutating the live view of the module like you are doing in the code above.
As far as I know - there is not way to mock a single function in a module, while testing that module, as this jest github issues talks about. What I'm wanting to know - is why this is.
Actually, it is possible.
"ES6 modules support cyclic dependencies automatically" which means that the live view of a module can be imported into the module itself.
As long as alpha calls beta using the live view of the module that beta is defined in, then beta can be mocked during the test. This works even if they are defined in the same module:
import * as indexModule from './index' // import the live view of the module
export function alpha(n) {
return `${n}${indexModule.beta(n)}${n}`; // call beta using the live view of the module
}
export function beta(n) {
return new Array(n).fill(0).map(() => ".").join("");
}
What I find interesting is that none of your code would work in a browser.
Module ("./some/path/to/file.js"):
const x = () => "x"
const y = () => "y"
export { x, y }
You cannot modify a named import as they are constants:
import { x } from "./some/path/to/file.js"
x = () => {} //Assignment to constant variable.
You also cannot assign to a readonly property of a namespace import.
import * as stuff from "./some/path/to/file.js"
stuff.y = () => {} //Cannot assign to read only property 'y' of...
Here's a codepen which also shows why indexModule.alpha !== alpha from the module: https://codepen.io/bluewater86/pen/QYwMPa
You are using the module to encapsulate your two functions but for the reasons above, this is a bad idea. You really need to encapsulate those functions in a class so that you can mock them appropriately.
//alphaBeta.js
export const beta = n => new Array(n).fill(0).map(() => ".").join("");
export default class alphaBeta {
static get beta() { return beta }
beta(n) {
beta(n)
}
alpha(n) {
return `${n}${this.beta(n)}${n}`;
}
}
export { alphaBeta }
And finally by moving to default/named imports instead of namespace imports you will have no need to use the cyclic dependency hack. Using default/named imports means that you will be importing the same in-memory view of the exports that the module exported. i.e. importer.beta === exporter.beta
import alphaBetaDefault, { alphaBeta, beta } from "./alphaBeta.js"
alphaBeta.prototype.beta = (n) => "x";
describe("alphaBeta", () => {
it("Imported function === exported function", () => {
expect(alphaBeta.beta).toEqual(beta); //PASS
});
const alphaBetaObject = new alphaBeta
it("Has been mocked", () => {
expect(alphaBetaObject.alpha(3)).toEqual("3x3");
});
alphaBeta.prototype.beta = (n) => "z";
it("Is still connected to its prototype", () => {
expect(alphaBetaObject.alpha(3)).toEqual("3z3");
});
const secondObject = new alphaBetaDefault
it("Will still be mocked for all imports of that module", () => {
expect(secondObject.alpha(3)).toEqual("3z3");
});
});

How to mock an asynchronous function call in another class

I have the following (simplified) React component.
class SalesView extends Component<{}, State> {
state: State = {
salesData: null
};
componentDidMount() {
this.fetchSalesData();
}
render() {
if (this.state.salesData) {
return <SalesChart salesData={this.state.salesData} />;
} else {
return <p>Loading</p>;
}
}
async fetchSalesData() {
let data = await new SalesService().fetchSalesData();
this.setState({ salesData: data });
}
}
When mounting, I fetch data from an API, which I have abstracted away in a class called SalesService. This class I want to mock, and for the method fetchSalesData I want to specify the return data (in a promise).
This is more or less how I want my test case to look like:
predefine test data
import SalesView
mock SalesService
setup mockSalesService to return a promise that returns the predefined test data when resolved
create the component
await
check snapshot
Testing the looks of SalesChart is not part of this question, I hope to solve that using Enzyme. I have been trying dozens of things to mock this asynchronous call, but I cannot seem to get this mocked properly. I have found the following examples of Jest mocking online, but they do not seem to cover this basic usage.
Hackernoon: Does not use asychronous calls
Wehkamp tech blog: Does not use asynchronous calls
Agatha Krzywda: Does not use asynchronous calls
GitConnected: Does not use a class with a function to mock
Jest tutorial An Async Example: Does not use a class with a function to mock
Jest tutorial Testing Asynchronous Code: Does not use a class with a function to mock
SO question 43749845: I can't connect the mock to the real implementation in this way
42638889: Is using dependency injection, I am not
46718663: Is not showing how the actual mock Class is implemented
My questions are:
How should the mock class look like?
Where should I place this mock class?
How should I import this mock class?
How do I tell that this mock class replaces the real class?
How do set up the mock implementation of a specific function of the mock class?
How do I wait in the test case for the promise to be resolved?
One example that I have that does not work is given below. The test runner crashes with the error throw err; and the last line in the stack trace is at process._tickCallback (internal/process/next_tick.js:188:7)
# __tests__/SalesView-test.js
import React from 'react';
import SalesView from '../SalesView';
jest.mock('../SalesService');
const salesServiceMock = require('../SalesService').default;
const weekTestData = [];
test('SalesView shows chart after SalesService returns data', async () => {
salesServiceMock.fetchSalesData.mockImplementation(() => {
console.log('Mock is called');
return new Promise((resolve) => {
process.nextTick(() => resolve(weekTestData));
});
});
const wrapper = await shallow(<SalesView/>);
expect(wrapper).toMatchSnapshot();
});
Sometimes, when a test is hard to write, it is trying to tell us that we have a design problem.
I think a small refactor could make things a lot easier - make SalesService a collaborator instead of an internal.
By that I mean, instead of calling new SalesService() inside your component, accept the sales service as a prop by the calling code. If you do that, then the calling code can also be your test, in which case all you need to do is mock the SalesService itself, and return whatever you want (using sinon or any other mocking library, or even just creating a hand rolled stub).
You could potentially abstract the new keyword away using a SalesService.create() method, then use jest.spyOn(object, methodName) to mock the implementation.
import SalesService from '../SalesService ';
test('SalesView shows chart after SalesService returns data', async () => {
const mockSalesService = {
fetchSalesData: jest.fn(() => {
return new Promise((resolve) => {
process.nextTick(() => resolve(weekTestData));
});
})
};
const spy = jest.spyOn(SalesService, 'create').mockImplementation(() => mockSalesService);
const wrapper = await shallow(<SalesView />);
expect(wrapper).toMatchSnapshot();
expect(spy).toHaveBeenCalled();
expect(mockSalesService.fetchSalesData).toHaveBeenCalled();
spy.mockReset();
spy.mockRestore();
});
One "ugly" way I've used in the past is to do a sort of poor-man's dependency injection.
It's based on the fact that you might not really want to go about instantiating SalesService every time you need it, but rather you want to hold a single instance per application, which everybody uses. In my case, SalesService required some initial configuration which I didn't want to repeat every time.[1]
So what I did was have a services.ts file which looks like this:
/// In services.ts
let salesService: SalesService|null = null;
export function setSalesService(s: SalesService) {
salesService = s;
}
export function getSalesService() {
if(salesService == null) throw new Error('Bad stuff');
return salesService;
}
Then, in my application's index.tsx or some similar place I'd have:
/// In index.tsx
// initialize stuff
const salesService = new SalesService(/* initialization parameters */)
services.setSalesService(salesService);
// other initialization, including calls to React.render etc.
In the components you can then just use getSalesService to get a reference to the one SalesService instance per application.
When it comes time to test, you just need to do some setup in your mocha (or whatever) before or beforeEach handlers to call setSalesService with a mock object.
Now, ideally, you'd want to pass in SalesService as a prop to your component, because it is an input to it, and by using getSalesService you're hiding this dependency and possibly causing you grief down the road. But if you need it in a very nested component, or if you're using a router or somesuch, it's becomes quite unwieldy to pass it as a prop.
You might also get away with using something like context, to keep everything inside React as it were.
The "ideal" solution for this would be something like dependency injection, but that's not an option with React AFAIK.
[1] It can also help in providing a single point for serializing remote-service calls, which might be needed at some point.

Categories

Resources