Testing and mocking a call with jest not working - reactjs

I'm trying to test the following function:
// playlist.js
export function getSimplePlaylist() {
// something here
}
export function getPlaylist(type, settings) {
let options = { type };
if (type === 'simple') {
options.getData = () => {
const { videos } = settings;
return getSimplePlaylist(videos);
};
}
// There are few more cases here, but no need to post them
return options;
}
I have tried a bunch of different ways of testing that, but no luck, i.e:
//playlist.spec.js
import * as playlist from '.';
describe('getPlaylist', () => {
beforeEach(() => {
jest.resetAllMocks();
});
it('should get correct option when static ', () => {
playlist.getSimplePlaylist = jest.fn();
const videos = playlist.getPlaylist('simple', { videos: [1, 2, 3] });
videos.getData()
expect(playlist.getSimplePlaylist).toBeCalled();
});
});
Any ideas on how I can test something like the above? Thanks!

If you use the function that way you use the JS functional programming. getPlayList will always call original getSimplePlaylist. If you want to do it you should use class:
class Playlist {
def() {
return 'def';
}
abc(cond) {
if (cond) {
return this.def();
}
}
}
export default Playlist;
and then you could test it:
import Playlist from './playlist';
describe('Name of the group', () => {
it('should ', () => {
const play = new Playlist();
play.def = jest.fn().mockReturnValue('mock');
expect(play.abc(true)).toEqual('mock');
expect(play.def).toBeCalled();
});
});
or you could use function with default implementation and then in test pass additional parameter:
// playlist.js
export function getSimplePlaylist() {
// something here
}
export function getPlaylist(type, settings, simplePlaylistFunc=getSimplePlaylist) {
let options = { type };
if (type === 'simple') {
options.getData = () => {
const { videos } = settings;
return simplePlaylistFunc(videos);
};
}
// There are few more cases here, but no need to post them
return options;
}

Probably the easiest solution here to mock function in a file would be to not export them individually but from an object and use it from that object in the particular module
// playlist.js
function getSimplePlaylist() {
// something here
}
function getPlaylist(type, settings) {
let options = { type };
if (type === 'simple') {
options.getData = () => {
const { videos } = settings;
return funcs.getSimplePlaylist(videos);
};
}
// There are few more cases here, but no need to post them
return options;
}
const funcs = {
getSimplePlaylist,
getPlaylist
}
export default funcs;
Now you can test them like
//playlist.spec.js
import playlist from '.';
describe('getPlaylist', () => {
beforeEach(() => {
jest.resetAllMocks();
});
it('should get correct option when static ', () => {
playlist.getSimplePlaylist = jest.fn();
const videos = playlist.getPlaylist('simple', { videos: [1, 2, 3] });
videos.getData()
expect(playlist.getSimplePlaylist).toBeCalled();
});
});

Related

How to test a browser supported feature in React testing library (IntersectionObserver is not defined) [duplicate]

I have a JavaScript component in my application that handles infinite scroll pagination, and i'm trying to rewrite it to use the IntersectionObserver, as described here, however I'm having issues in testing it.
Is there a way to drive the behavior of the observer in a QUnit test, i.e. to trigger the observer callback with some entries described in my tests?
A possible solution I have come up with is to expose the callback function in the component's prototype and to invoke it directly in my test, something like this:
InfiniteScroll.prototype.observerCallback = function(entries) {
//handle the infinite scroll
}
InfiniteScroll.prototype.initObserver = function() {
var io = new IntersectionObserver(this.observerCallback);
io.observe(someElements);
}
//In my test
var component = new InfiniteScroll();
component.observerCallback(someEntries);
//Do some assertions about the state after the callback has been executed
I don't really like this approach since it's exposing the fact that the component uses an IntersectionObserver internally, which is an implementation detail that in my opinion should not be visible to client code, so is there any better way to test this (ideally not using jQuery)?
None of the posted answered worked for me because of our configuration of TypeScript and React (tsx) we're using. Here's what finally worked:
beforeEach(() => {
// IntersectionObserver isn't available in test environment
const mockIntersectionObserver = jest.fn();
mockIntersectionObserver.mockReturnValue({
observe: () => null,
unobserve: () => null,
disconnect: () => null
});
window.IntersectionObserver = mockIntersectionObserver;
});
In your jest.setup.js file, mock the IntersectionObserver with the following implementation:
global.IntersectionObserver = class IntersectionObserver {
constructor() {}
disconnect() {
return null;
}
observe() {
return null;
}
takeRecords() {
return null;
}
unobserve() {
return null;
}
};
Instead of using the Jest Setup File, you can do this mocking also directly in your tests or in your beforeAll,beforeEach blocks.
Here's another alternative based on previous answers, you can run it inside the beforeEach methods, or at the beginning of the .test.js file.
You could also pass parameters to the setupIntersectionObserverMock to mock the observe and/or unobserve methods to spy on them with a jest.fn() mock function.
/**
* Utility function that mocks the `IntersectionObserver` API. Necessary for components that rely
* on it, otherwise the tests will crash. Recommended to execute inside `beforeEach`.
* #param intersectionObserverMock - Parameter that is sent to the `Object.defineProperty`
* overwrite method. `jest.fn()` mock functions can be passed here if the goal is to not only
* mock the intersection observer, but its methods.
*/
export function setupIntersectionObserverMock({
root = null,
rootMargin = '',
thresholds = [],
disconnect = () => null,
observe = () => null,
takeRecords = () => [],
unobserve = () => null,
} = {}) {
class MockIntersectionObserver {
constructor() {
this.root = root;
this.rootMargin = rootMargin;
this.thresholds = thresholds;
this.disconnect = disconnect;
this.observe = observe;
this.takeRecords = takeRecords;
this.unobserve = unobserve;
}
}
Object.defineProperty(window, 'IntersectionObserver', {
writable: true,
configurable: true,
value: MockIntersectionObserver
});
Object.defineProperty(global, 'IntersectionObserver', {
writable: true,
configurable: true,
value: MockIntersectionObserver
});
}
And for TypeScript:
/**
* Utility function that mocks the `IntersectionObserver` API. Necessary for components that rely
* on it, otherwise the tests will crash. Recommended to execute inside `beforeEach`.
* #param intersectionObserverMock - Parameter that is sent to the `Object.defineProperty`
* overwrite method. `jest.fn()` mock functions can be passed here if the goal is to not only
* mock the intersection observer, but its methods.
*/
export function setupIntersectionObserverMock({
root = null,
rootMargin = '',
thresholds = [],
disconnect = () => null,
observe = () => null,
takeRecords = () => [],
unobserve = () => null,
} = {}): void {
class MockIntersectionObserver implements IntersectionObserver {
readonly root: Element | null = root;
readonly rootMargin: string = rootMargin;
readonly thresholds: ReadonlyArray < number > = thresholds;
disconnect: () => void = disconnect;
observe: (target: Element) => void = observe;
takeRecords: () => IntersectionObserverEntry[] = takeRecords;
unobserve: (target: Element) => void = unobserve;
}
Object.defineProperty(
window,
'IntersectionObserver', {
writable: true,
configurable: true,
value: MockIntersectionObserver
}
);
Object.defineProperty(
global,
'IntersectionObserver', {
writable: true,
configurable: true,
value: MockIntersectionObserver
}
);
}
Same problem in 2019 this is how I solved it:
import ....
describe('IntersectionObserverMokTest', () => {
...
const observeMock = {
observe: () => null,
disconnect: () => null // maybe not needed
};
beforeEach(async(() => {
(<any> window).IntersectionObserver = () => observeMock;
....
}));
it(' should run the Test without throwing an error for the IntersectionObserver', () => {
...
})
});
So I create a mock object, with the observe (and disconnect) method and overwrite the IntersectionObserver on the window object. Depending on your usage, you might have to overwrite other functions (see: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#Browser_compatibility )
The code is inspired by https://gist.github.com/ianmcnally/4b68c56900a20840b6ca840e2403771c but doesn't use jest
I tested it like this for Jest+Typescript
type CB = (arg1: IntersectionObserverEntry[]) => void;
class MockedObserver {
cb: CB;
options: IntersectionObserverInit;
elements: HTMLElement[];
constructor(cb: CB, options: IntersectionObserverInit) {
this.cb = cb;
this.options = options;
this.elements = [];
}
unobserve(elem: HTMLElement): void {
this.elements = this.elements.filter((en) => en !== elem);
}
observe(elem: HTMLElement): void {
this.elements = [...new Set(this.elements.concat(elem))];
}
disconnect(): void {
this.elements = [];
}
fire(arr: IntersectionObserverEntry[]): void {
this.cb(arr);
}
}
function traceMethodCalls(obj: object | Function, calls: any = {}) {
const handler: ProxyHandler<object | Function> = {
get(target, propKey, receiver) {
const targetValue = Reflect.get(target, propKey, receiver);
if (typeof targetValue === 'function') {
return function (...args: any[]) {
calls[propKey] = (calls[propKey] || []).concat(args);
// eslint-disable-next-line #typescript-eslint/ban-ts-comment
// #ts-ignore
return targetValue.apply(this, args);
};
} else {
return targetValue;
}
},
};
return new Proxy(obj, handler);
}
And in test
describe('useIntersectionObserver', () => {
let observer: any;
let mockedObserverCalls: { [k: string]: any } = {};
beforeEach(() => {
Object.defineProperty(window, 'IntersectionObserver', {
writable: true,
value: jest
.fn()
.mockImplementation(function TrackMock(
cb: CB,
options: IntersectionObserverInit
) {
observer = traceMethodCalls(
new MockedObserver(cb, options),
mockedObserverCalls
);
return observer;
}),
});
});
afterEach(() => {
observer = null;
mockedObserverCalls = {};
});
test('should do something', () => {
const mockedObserver = observer as unknown as MockedObserver;
const entry1 = {
target: new HTMLElement(),
intersectionRatio: 0.7,
};
// fire CB
mockedObserver.fire([entry1 as unknown as IntersectionObserverEntry]);
// possibly need to make test async/wait for see changes
// await waitForNextUpdate();
// await waitForDomChanges();
// await new Promise((resolve) => setTimeout(resolve, 0));
// Check calls to observer
expect(mockedObserverCalls.disconnect).toEqual([]);
expect(mockedObserverCalls.observe).toEqual([]);
});
});
I had this problem with a setup based on vue-cli. I ended up using a mix of the answers I saw above:
const mockIntersectionObserver = class {
constructor() {}
observe() {}
unobserve() {}
disconnect() {}
};
beforeEach(() => {
window.IntersectionObserver = mockIntersectionObserver;
});
Had a similar stack issue as #Kevin Brotcke, except using their solution resulted in a further TypeScript error:
Function expression, which lacks return-type annotation, implicitly has an 'any' return type.
Here's a tweaked solution that worked for me:
beforeEach(() => {
// IntersectionObserver isn't available in test environment
const mockIntersectionObserver = jest.fn()
mockIntersectionObserver.mockReturnValue({
observe: jest.fn().mockReturnValue(null),
unobserve: jest.fn().mockReturnValue(null),
disconnect: jest.fn().mockReturnValue(null)
})
window.IntersectionObserver = mockIntersectionObserver
})

Jest localStorage test [duplicate]

I keep getting "localStorage is not defined" in Jest tests which makes sense but what are my options? Hitting brick walls.
Great solution from #chiedo
However, we use ES2015 syntax and I felt it was a little cleaner to write it this way.
class LocalStorageMock {
constructor() {
this.store = {};
}
clear() {
this.store = {};
}
getItem(key) {
return this.store[key] || null;
}
setItem(key, value) {
this.store[key] = String(value);
}
removeItem(key) {
delete this.store[key];
}
}
global.localStorage = new LocalStorageMock;
Figured it out with help from this: https://groups.google.com/forum/#!topic/jestjs/9EPhuNWVYTg
Setup a file with the following contents:
var localStorageMock = (function() {
var store = {};
return {
getItem: function(key) {
return store[key];
},
setItem: function(key, value) {
store[key] = value.toString();
},
clear: function() {
store = {};
},
removeItem: function(key) {
delete store[key];
}
};
})();
Object.defineProperty(window, 'localStorage', { value: localStorageMock });
Then you add the following line to your package.json under your Jest configs
"setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",
Answer:
Currently (Jul '22) localStorage can not be mocked or spied on by jest as you usually would, and as outlined in the create-react-app docs. This is due to changes made in jsdom. You can read about it in the jest and jsdom issue trackers.
As a workaround, you can spy on the prototype instead:
// does not work:
jest.spyOn(localStorage, "setItem");
localStorage.setItem = jest.fn();
// either of these lines will work, different syntax that does the same thing:
jest.spyOn(Storage.prototype, 'setItem');
Storage.prototype.setItem = jest.fn();
// assertions as usual:
expect(localStorage.setItem).toHaveBeenCalled();
A note on spying on the prototype:
Spying on an instance gives you the ability to observe and mock behaviour for a specific object.
Spying on the prototype, on the other hand, will observe/manipulate every instance of that class all at once. Unless you have a special usecase, this is probably not what you want.
However, in this case it makes no difference, because there only exists a single instance of localStorage.
If using create-react-app, there is a simpler and straightforward solution explained in the documentation.
Create src/setupTests.js and put this in it :
const localStorageMock = {
getItem: jest.fn(),
setItem: jest.fn(),
clear: jest.fn()
};
global.localStorage = localStorageMock;
Tom Mertz contribution in a comment below :
You can then test that your localStorageMock's functions are used by doing something like
expect(localStorage.getItem).toBeCalledWith('token')
// or
expect(localStorage.getItem.mock.calls.length).toBe(1)
inside of your tests if you wanted to make sure it was called. Check out https://facebook.github.io/jest/docs/en/mock-functions.html
Unfortunately, the solutions that I've found here didn't work for me.
So I was looking at Jest GitHub issues and found this thread
The most upvoted solutions were these ones:
const spy = jest.spyOn(Storage.prototype, 'setItem');
// or
Storage.prototype.getItem = jest.fn(() => 'bla');
A better alternative which handles undefined values (it doesn't have toString()) and returns null if value doesn't exist. Tested this with react v15, redux and redux-auth-wrapper
class LocalStorageMock {
constructor() {
this.store = {}
}
clear() {
this.store = {}
}
getItem(key) {
return this.store[key] || null
}
setItem(key, value) {
this.store[key] = value
}
removeItem(key) {
delete this.store[key]
}
}
global.localStorage = new LocalStorageMock
or you just take a mock package like this:
https://www.npmjs.com/package/jest-localstorage-mock
it handles not only the storage functionality but also allows you test if the store was actually called.
If you are looking for a mock and not a stub, here is the solution I use:
export const localStorageMock = {
getItem: jest.fn().mockImplementation(key => localStorageItems[key]),
setItem: jest.fn().mockImplementation((key, value) => {
localStorageItems[key] = value;
}),
clear: jest.fn().mockImplementation(() => {
localStorageItems = {};
}),
removeItem: jest.fn().mockImplementation((key) => {
localStorageItems[key] = undefined;
}),
};
export let localStorageItems = {}; // eslint-disable-line import/no-mutable-exports
I export the storage items for easy initialization. I.E. I can easily set it to an object
In the newer versions of Jest + JSDom it is not possible to set this, but the localstorage is already available and you can spy on it it like so:
const setItemSpy = jest.spyOn(Object.getPrototypeOf(window.localStorage), 'setItem');
For Jest, React & TypeScript users:
I created a mockLocalStorage.ts
export const mockLocalStorage = () => {
const setItemMock = jest.fn();
const getItemMock = jest.fn();
beforeEach(() => {
Storage.prototype.setItem = setItemMock;
Storage.prototype.getItem = getItemMock;
});
afterEach(() => {
setItemMock.mockRestore();
getItemMock.mockRestore();
});
return { setItemMock, getItemMock };
};
My component:
export const Component = () => {
const foo = localStorage.getItem('foo')
localStorage.setItem('bar', 'true')
return <h1>{foo}</h1>
}
then in my tests I use it like so:
import React from 'react';
import { mockLocalStorage } from '../../test-utils';
import { Component } from './Component';
const { getItemMock, setItemMock } = mockLocalStorage();
it('fetches something from localStorage', () => {
getItemMock.mockReturnValue('bar');
render(<Component />);
expect(getItemMock).toHaveBeenCalled();
expect(getByText(/bar/i)).toBeInTheDocument()
});
it('expects something to be set in localStorage' () => {
const value = "true"
const key = "bar"
render(<Component />);
expect(setItemMock).toHaveBeenCalledWith(key, value);
}
I found this solution from github
var localStorageMock = (function() {
var store = {};
return {
getItem: function(key) {
return store[key] || null;
},
setItem: function(key, value) {
store[key] = value.toString();
},
clear: function() {
store = {};
}
};
})();
Object.defineProperty(window, 'localStorage', {
value: localStorageMock
});
You can insert this code in your setupTests and it should work fine.
I tested it in a project with typesctipt.
A bit more elegant solution using TypeScript and Jest.
interface Spies {
[key: string]: jest.SpyInstance
}
describe('→ Local storage', () => {
const spies: Spies = {}
beforeEach(() => {
['setItem', 'getItem', 'clear'].forEach((fn: string) => {
const mock = jest.fn(localStorage[fn])
spies[fn] = jest.spyOn(Storage.prototype, fn).mockImplementation(mock)
})
})
afterEach(() => {
Object.keys(spies).forEach((key: string) => spies[key].mockRestore())
})
test('→ setItem ...', async () => {
localStorage.setItem( 'foo', 'bar' )
expect(localStorage.getItem('foo')).toEqual('bar')
expect(spies.setItem).toHaveBeenCalledTimes(1)
})
})
You can use this approach, to avoid mocking.
Storage.prototype.getItem = jest.fn(() => expectedPayload);
Object.defineProperty(window, "localStorage", {
value: {
getItem: jest.fn(),
setItem: jest.fn(),
removeItem: jest.fn(),
},
});
or
jest.spyOn(Object.getPrototypeOf(localStorage), "getItem");
jest.spyOn(Object.getPrototypeOf(localStorage), "setItem");
As #ck4 suggested documentation has clear explanation for using localStorage in jest. However the mock functions were failing to execute any of the localStorage methods.
Below is the detailed example of my react component which make uses of abstract methods for writing and reading data,
//file: storage.js
const key = 'ABC';
export function readFromStore (){
return JSON.parse(localStorage.getItem(key));
}
export function saveToStore (value) {
localStorage.setItem(key, JSON.stringify(value));
}
export default { readFromStore, saveToStore };
Error:
TypeError: _setupLocalStorage2.default.setItem is not a function
Fix:
Add below mock function for jest (path: .jest/mocks/setUpStore.js )
let mockStorage = {};
module.exports = window.localStorage = {
setItem: (key, val) => Object.assign(mockStorage, {[key]: val}),
getItem: (key) => mockStorage[key],
clear: () => mockStorage = {}
};
Snippet is referenced from here
To do the same in the Typescript, do the following:
Setup a file with the following contents:
let localStorageMock = (function() {
let store = new Map()
return {
getItem(key: string):string {
return store.get(key);
},
setItem: function(key: string, value: string) {
store.set(key, value);
},
clear: function() {
store = new Map();
},
removeItem: function(key: string) {
store.delete(key)
}
};
})();
Object.defineProperty(window, 'localStorage', { value: localStorageMock });
Then you add the following line to your package.json under your Jest configs
"setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",
Or you import this file in your test case where you want to mock the localstorage.
describe('getToken', () => {
const Auth = new AuthService();
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Ik1yIEpvc2VwaCIsImlkIjoiNWQwYjk1Mzg2NTVhOTQ0ZjA0NjE5ZTA5IiwiZW1haWwiOiJ0cmV2X2pvc0Bob3RtYWlsLmNvbSIsInByb2ZpbGVVc2VybmFtZSI6Ii9tcmpvc2VwaCIsInByb2ZpbGVJbWFnZSI6Ii9Eb3Nlbi10LUdpci1sb29rLWN1dGUtbnVrZWNhdDMxNnMtMzExNzAwNDYtMTI4MC04MDAuanBnIiwiaWF0IjoxNTYyMzE4NDA0LCJleHAiOjE1OTM4NzYwMDR9.YwU15SqHMh1nO51eSa0YsOK-YLlaCx6ijceOKhZfQZc';
beforeEach(() => {
global.localStorage = jest.fn().mockImplementation(() => {
return {
getItem: jest.fn().mockReturnValue(token)
}
});
});
it('should get the token from localStorage', () => {
const result = Auth.getToken();
expect(result).toEqual(token);
});
});
Create a mock and add it to the global object
At least as of now, localStorage can be spied on easily on your jest tests, for example:
const spyRemoveItem = jest.spyOn(window.localStorage, 'removeItem')
And that's it. You can use your spy as you are used to.
This worked for me and just one code line
const setItem = jest.spyOn(Object.getPrototypeOf(localStorage), 'setItem');
2021, typescript
class LocalStorageMock {
store: { [k: string]: string };
length: number;
constructor() {
this.store = {};
this.length = 0;
}
/**
* #see https://developer.mozilla.org/en-US/docs/Web/API/Storage/key
* #returns
*/
key = (idx: number): string => {
const values = Object.values(this.store);
return values[idx];
};
clear() {
this.store = {};
}
getItem(key: string) {
return this.store[key] || null;
}
setItem(key: string, value: string) {
this.store[key] = String(value);
}
removeItem(key: string) {
delete this.store[key];
}
}
export default LocalStorageMock;
you can then use it with
global.localStorage = new LocalStorageMock();
Riffed off some other answers here to solve it for a project with Typescript. I created a LocalStorageMock like this:
export class LocalStorageMock {
private store = {}
clear() {
this.store = {}
}
getItem(key: string) {
return this.store[key] || null
}
setItem(key: string, value: string) {
this.store[key] = value
}
removeItem(key: string) {
delete this.store[key]
}
}
Then I created a LocalStorageWrapper class that I use for all access to local storage in the app instead of directly accessing the global local storage variable. Made it easy to set the mock in the wrapper for tests.
As mentioned in a comment by Niket Pathak,
starting jest#24 / jsdom#11.12.0 and above, localStorage is mocked automatically.
An update for 2022.
Jest#24+ has ability to mock local storage automatically. However, the dependency needed no longer ships with it by default.
npm i -D jest-environment-jsdom
You also need to change your Jest test mode:
// jest.config.cjs
module.exports = {
...
testEnvironment: "jsdom",
...
};
Now localStorage will already be mocked for you.
Example:
// myStore.js
const saveLocally = (key, value) => {
localStorage.setItem(key, value)
};
Test:
// myStore.spec.ts
import { saveLocally } from "./myStore.js"
it("saves key-value pair", () => {
let key = "myKey";
let value = "myValue";
expect(localStorage.getItem(key)).toBe(null);
saveLocally(key, value);
expect(localStorage.getItem(key)).toBe(value);
};
The following solution is compatible for testing with stricter TypeScript, ESLint, TSLint, and Prettier config: { "proseWrap": "always", "semi": false, "singleQuote": true, "trailingComma": "es5" }:
class LocalStorageMock {
public store: {
[key: string]: string
}
constructor() {
this.store = {}
}
public clear() {
this.store = {}
}
public getItem(key: string) {
return this.store[key] || undefined
}
public setItem(key: string, value: string) {
this.store[key] = value.toString()
}
public removeItem(key: string) {
delete this.store[key]
}
}
/* tslint:disable-next-line:no-any */
;(global as any).localStorage = new LocalStorageMock()
HT/ https://stackoverflow.com/a/51583401/101290 for how to update global.localStorage
There is no need to mock localStorage - just use the jsdom environment so that your tests run in browser-like conditions.
In your jest.config.js,
module.exports = {
// ...
testEnvironment: "jsdom"
}
none of the answers above worked for me. So after some digging this is what I got to work. Credit goes to a few sources and other answers as well.
https://www.codeblocq.com/2021/01/Jest-Mock-Local-Storage/
https://github.com/facebook/jest/issues/6798#issuecomment-440988627
https://gist.github.com/mayank23/7b994385eb030f1efb7075c4f1f6ac4c
https://github.com/facebook/jest/issues/6798#issuecomment-514266034
My full gist: https://gist.github.com/ar-to/01fa07f2c03e7c1b2cfe6b8c612d4c6b
/**
* Build Local Storage object
* #see https://www.codeblocq.com/2021/01/Jest-Mock-Local-Storage/ for source
* #see https://stackoverflow.com/a/32911774/9270352 for source
* #returns
*/
export const fakeLocalStorage = () => {
let store: { [key: string]: string } = {}
return {
getItem: function (key: string) {
return store[key] || null
},
setItem: function (key: string, value: string) {
store[key] = value.toString()
},
removeItem: function (key: string) {
delete store[key]
},
clear: function () {
store = {}
},
}
}
/**
* Mock window properties for testing
* #see https://gist.github.com/mayank23/7b994385eb030f1efb7075c4f1f6ac4c for source
* #see https://github.com/facebook/jest/issues/6798#issuecomment-514266034 for sample implementation
* #see https://developer.mozilla.org/en-US/docs/Web/API/Window#properties for window properties
* #param { string } property window property string but set to any due to some warnings
* #param { Object } value for property
*
* #example
*
* const testLS = {
* id: 5,
* name: 'My Test',
* }
* mockWindowProperty('localStorage', fakeLocalStorage())
* window.localStorage.setItem('currentPage', JSON.stringify(testLS))
*
*/
const mockWindowProperty = (property: string | any, value: any) => {
const { [property]: originalProperty } = window
delete window[property]
beforeAll(() => {
Object.defineProperty(window, property, {
configurable: true,
writable: true,
value,
})
})
afterAll(() => {
window[property] = originalProperty
})
}
export default mockWindowProperty
In my case, I needed to set the localStorage value before I check it.
So what I did is
const data = { .......}
const setLocalStorageValue = (name: string, value: any) => {
localStorage.setItem(name, JSON.stringify(value))
}
describe('Check X class', () => {
setLocalStorageValue('Xname', data)
const xClass= new XClass()
console.log(xClass.initiate()) ; // it will work
})
2022 December: Nx 14 with Angular 14 Jest.
We have an test-setup.ts file in every app and libs folder. We setting local storage mock globaly.
import 'jest-preset-angular/setup-jest';
Storage.prototype.getItem = jest.fn();
Storage.prototype.setItem = jest.fn();
Storage.prototype.removeItem = jest.fn();
Then localStorage.service.spec.ts file looking like this:
import { LocalStorageService } from './localstorage.service';
describe('LocalStorageService', () => {
let localStorageService: LocalStorageService;
beforeEach(() => {
localStorageService = new LocalStorageService();
});
it('should set "identityKey" in localStorage', async () => {
localStorageService.saveData('identityKey', '99');
expect(window.localStorage.setItem).toHaveBeenCalled();
expect(window.localStorage.setItem).toHaveBeenCalledWith('identityKey', '99');
expect(window.localStorage.setItem).toHaveBeenCalledTimes(1);
});
it('should get "identityKey" from localStorage', async () => {
localStorageService.getData('identityKey');
expect(window.localStorage.getItem).toHaveBeenCalled();
expect(window.localStorage.getItem).toHaveBeenCalledWith('identityKey');
expect(window.localStorage.getItem).toHaveBeenCalledTimes(1);
});
it('should remove "identityKey" from localStorage', async () => {
localStorageService.removeData('identityKey');
expect(window.localStorage.removeItem).toHaveBeenCalled();
expect(window.localStorage.removeItem).toHaveBeenCalledWith('identityKey');
expect(window.localStorage.removeItem).toHaveBeenCalledTimes(1);
});
});
P.S. Sorry for bad indentation, this SatckOverflow window s*cks.
First: I created a file named localStorage.ts(localStorage.js)
class LocalStorageMock {
store: Store;
length: number;
constructor() {
this.store = {};
this.length = 0;
}
key(n: number): any {
if (typeof n === 'undefined') {
throw new Error(
"Uncaught TypeError: Failed to execute 'key' on 'Storage': 1 argument required, but only 0 present."
);
}
if (n >= Object.keys(this.store).length) {
return null;
}
return Object.keys(this.store)[n];
}
getItem(key: string): Store | null {
if (!Object.keys(this.store).includes(key)) {
return null;
}
return this.store[key];
}
setItem(key: string, value: any): undefined {
if (typeof key === 'undefined' && typeof value === 'undefined') {
throw new Error(
"Uncaught TypeError: Failed to execute 'setItem' on 'Storage': 2 arguments required, but only 0 present."
);
}
if (typeof value === 'undefined') {
throw new Error(
"Uncaught TypeError: Failed to execute 'setItem' on 'Storage': 2 arguments required, but only 1 present."
);
}
if (!key) return undefined;
this.store[key] = value.toString() || '';
this.length = Object.keys(this.store).length;
return undefined;
}
removeItem(key: string): undefined {
if (typeof key === 'undefined') {
throw new Error(
"Uncaught TypeError: Failed to execute 'removeItem' on 'Storage': 1 argument required, but only 0 present."
);
}
delete this.store[key];
this.length = Object.keys(this.store).length;
return undefined;
}
clear(): undefined {
this.store = {};
this.length = 0;
return undefined;
}
}
export const getLocalStorageMock = (): any => {
return new LocalStorageMock();
};
global.localStorage = new LocalStorageMock();
Then create a test file named session.test.ts(session.test.js)
import { getLocalStorageMock } from '../localstorage';
describe('session storage', () => {
let localStorage;
beforeEach(() => {
localStorage = getLocalStorageMock();
});
describe('getItem', () => {
it('should return null if the item is undefined', () => {
expect(localStorage.getItem('item')).toBeNull();
});
it("should return '' instead of null", () => {
localStorage.setItem('item', '');
expect(localStorage.getItem('item')).toBe('');
});
it('should return navid', () => {
localStorage.setItem('item', 'navid');
expect(localStorage.getItem('item')).toBe('navid');
});
});
});
This worked for me,
delete global.localStorage;
global.localStorage = {
getItem: () =>
}

Reusing the result of an http call

I have the following use case:
Two visual grids are using two methods to load the data to display. These methods are automatically called by the grids and this part cannot be changed, it's by design:
loadDataForGrid1 = (params: any): any => {
return this.httpService.getData().then((response) => {
return response.dataGrid1;
}, (err) => {
});
}
loadDataForGrid2 = (params: any): any => {
return this.httpService.getData().then((response) => {
return response.dataGrid2;
}, (err) => {
});
}
Everything is working fine but my problem is performance. Since the getData method does an http request that is quite huge, calling it twice like Im doing right now is not acceptable. It there a way to solve this problem by doing only one call? Like caching the data so that they are reusable by the second call?
Im using typescript and angularjs
Edit:
Something like this would not work since the result would not be available when the grids load the data:
result: any;
// called at the beginning, for example contructor
loadData = (params: any): any => {
return this.httpService.getData().then(result => {
this.result = result;
});
}
loadDataForGrid1 = (params: any): any => {
return this.result.gridGrid1;
}
loadDataForGrid2 = (params: any): any => {
return this.result.gridGrid2;
}}
Using the answer suggested by #georgeawg generates the following javascript (which does 2 calls)
this.loadDataForGrid1 = function (params) {
_this.promiseCache = _this.promiseCache || _this.httpService.getData();
return _this.promiseCache.then(function (response) {
return response.gridGrid1;
}, function (err) {
});
};
this.loadDataForGrid2 = function (params) {
_this.promiseCache = _this.promiseCache || _this.httpService.getData();
return _this.promiseCache.then(function (response) {
return response.gridGrid2;
}, function (err) {
});
};
You can always store the the data array in a variable on the page for SPA. If you want to use the data over different pages, you can use localStorage to 'cache' the data on the client-side.
localStorage.set("mydata", response.dataGrid1);
localStorage.get("mydata");
FYI, i does not seem you are using typescript, but rather native javascript :-)
--
Why don't you do something like this, or am i missing something?
$scope.gridData = {};
loadDataForGrid1 = (params: any): any => {
return this.httpService.getData.then((response) => {
$scope.gridData = response;
}, (err) => {
}).finally(function(){
console.log($scope.gridData.gridData1);
console.log($scope.gridData.gridData2);
});
}
What you can do is store the returned variable into a service variable and then do a check if it already exists.
dataGrid;
loadDataForGrid1 = (params: any): any => {
if(!this.dataGrid) {
return this.httpService.getData.then((response) => {
this.dataGrid = response;
return this.dataGrid.dataGrid1;
}, (err) => {
});
}
return this.dataGrid.dataGrid1;
}
loadDataForGrid2 = (params: any): any => {
if(!this.dataGrid) {
return this.httpService.getData().then((response) => {
this.dataGrid = response;
return this.dataGrid.dataGrid2;
}, (err) => {
});
}
return this.dataGrid.dataGrid2;
}
Something like this should work. Every time you call loadDataForGrid1 or loadDataForGrid2 you will first check if the data is already there - therefore you make an API call only once.
The solution is to cache the promise and re-use it:
var promiseCache;
this.loadDataForGrid1 = (params) => {
promiseCache = promiseCache || this.httpService.getData();
return promiseCache.then(result => {
return result.gridGrid1;
});
}
this.loadDataForGrid2 = (params) => {
promiseCache = promiseCache || this.httpService.getData();
return promiseCache.then(result => {
return result.gridGrid2;
});
}
Since the service immediately returns a promise, it avoids the race condition where the
second XHR is started before the first XHR returns data from the server.
You mean that would be a javascript solution? But how to do it with typescript then?
JavaScript supports private variables.1
function MyClass() {
var myPrivateVar = 3;
this.doSomething = function() {
return myPrivateVar++;
}
}
In TypeScript this would be expressed like so:
class MyClass {
doSomething: () => number;
constructor() {
var myPrivateVar = 3;
this.doSomething = function () {
return myPrivateVar++;
}
}
}
So, after many hours I came to the following solution. It's a bit a hack but it works.
In the initialization (constructor or so) Im loading the data:
this.httpService.getData().then((response) => {
this.data1 = response.dataGrid1;
this.data2 = response.dataGrid2;
// other properties here...
this.isReady= true;
}, (err) => {
});
then I wrote an ugly wait method
wait(): void {
if (this.isReady) {
return;
} else {
setTimeout(this.wait, 250);
}
}
Finally, my two methods look like this
loadDataForGrid1 = (params: any): any => {
this.wait();
return this.$q.resolve(this.data1);
}
loadDataForGrid2 = (params: any): any => {
this.wait();
return this.$q.resolve(this.data2);
}

Unit test for a function calling another function using jest

I'm new to Jest and having some issues to write the unit test.
My function is calling another anonymous function with some parameters.
Could you please help me fix it?
const myFunctionToTest = (code, data) => (isValid, availableCodes, defaultValue) => {
if(isValid) {
const isAvailableCode = isEmpty(availableCodes) || includes(availableCodes, code);
return isAvailableCode ? get(data, 'originalQty') : defaultValue;
}
return defaultValue;
};
Here's the mock data:
Mock data:
code: 'AB'
data: { originalQty : 2 };
isValid: true;
availableCodes: ['BCD', 'AB'];
defaultValue: 0;
What I tried!
describe('myFunctionToTest', () => {
test('it should return originally assigned quantity', () => {
const result = myFunctionToTest('AB', { originalQty: 2 } , () => {true, ['BCD', 'AB'], 0});
expect(result).toEqual(2);
});
});
Ok, I got it.
This is what I tried!
describe('myFunctionToTest', () => {
test('it should return originally assigned quantity', () => {
const result = myFunctionToTest('AB', { originalQty: 2 });
expect(result(true, ['BCD', 'AB'], 0).toEqual(2);
});
});

how to set state in react componentDidMount with method?

code is like this:
componentDidMount() {
this.setState(({getPublicTodosLength}, props) => ({
getPublicTodosLength: () => this.getPublicTodosLengthForPagination() // no returned value
}));
}
getPublicTodosLengthForPagination = async () => { // get publicTodos length since we cannot get it declared on createPaginationContainer
const getPublicTodosLengthQueryText = `
query TodoListHomeQuery {# filename+Query
viewer {
publicTodos {
edges {
node {
id
}
}
}
}
}`
const getPublicTodosLengthQuery = { text: getPublicTodosLengthQueryText }
const result = await this.props.relay.environment._network.fetch(getPublicTodosLengthQuery, {})
return result.data.viewer.publicTodos.edges.length;
}
getPublicTodosLengthForPagination is not invoked and the returned value is not assigned.Also, When i invoke it right away e.g. without () => it's assigned value is a promise? I am expecting int/number, the return value of edges.length. help?
The returned value is not assigned because you are not invoking the function rather assigning it.
componentDidMount() {
this.setState(({getPublicTodosLength}, props) => ({
getPublicTodosLength: this.getPublicTodosLengthForPagination()
}));
}
I'm not sure why you're setting state like that, maybe you could help explain what you're doing. In the meantime shouldn't it be written like this:
componentDidMount() {
this.setState({
getPublicTodosLength: await this.getPublicTodosLengthForPagination() // no returned value
});
}
getPublicTodosLengthForPagination = async () => { // get publicTodos length since we cannot get it declared on createPaginationContainer
const getPublicTodosLengthQueryText = `
query TodoListHomeQuery {# filename+Query
viewer {
publicTodos {
edges {
node {
id
}
}
}
}
}`
const getPublicTodosLengthQuery = { text: getPublicTodosLengthQueryText }
const result = await this.props.relay.environment._network.fetch(getPublicTodosLengthQuery, {})
return result.data.viewer.publicTodos.edges.length;
}

Resources