How to use localStorage in unit tests with Chai - javascript

I have an app with react and redux. My test engine is - Chai
In my reducer (src/my_reducer.js), I try to get token from localStorage like this:
const initialState = {
profile: {},
token: window.localStorage.getItem('id_token') ? window.localStorage.getItem('id_token') : null,
}
In my tests file (test/reducer_spec.js) I have import 'my_reducer' before test cases:
import myReducer from '../src/my_reducer'
And I have an error, if I try to run test - localStorage (or window.localStorage) - undefined.
I need to mock localStorage? If I need, where is the place for it?

I presume you are running your tests with mocha?
mocha tests run in node.js, and node.js does not have a global window variable. But you can easily create one in your tests:
global.window = {};
You can even add the localStorage to it immediately:
global.window = { localStorage: /* your mock localStorage */ }
The mock depends on what you store in your local storage, but for the example code above this might be a reasonable mock object:
var mockLocalStorage = {
getItem: function (key) {
if( key === 'id_token' ){ return /* a token object */; }
return null;
}
}
Of course, for different tests you can have different mocks, e.g. another mock might always return null to test the case that the key cannot be found.

I solve problem with mock-local-storage
My run test command is:
mocha -r mock-local-storage --compilers js:babel-core/register --recursive

For testing purposes I recommend not to make any calls which may have side effects or call external modules in declarations.
Because requiring / importing your reducer implicitly calls window.localStorage.getItem(...) clean testing gets hard.
I'd suggest to wrap your initialization code with a init method so nothing happens if you require/import your module before calling init. Then you can use beforeEach afterEach to cleanly setup mocks/sandboxes.
import myReducer from '../src/my_reducer'
describe('with faked localStorage', function() {
var sandbox
beforeEach(function() {
sandbox = sinon.sandbox.create()
// fake window.localStorage
})
afterEach(function() {
sandbox.restore()
})
describe('the reducer', function() {
before(function() {
myReducer.init()
})
})
})
The second best solution is to postpone the import and use require within the before test hook.
describe('with fake localStorage', function() {
var sandbox
beforeEach(function() {
sandbox = sinon.sandbox.create()
// fake window.localStorage
})
afterEach(function() {
sandbox.restore()
})
describe('the reducer', function() {
var myReducer
before(function() {
myReducer = require('../src/my_reducer')
})
})
})

It is because you are not running Chai in a browser environment.
Try:
// Make sure there is a window object available, and that it has localstorage (old browsers don't)
const initialState = {
profile: {},
// window.localStorage.getItem('id_token') will return null if key not found
token: window && window.localStorage ? window.localStorage.getItem('id_token') : null,
}

Related

jest.mock not working with Javascript test and Typescript module

My mocked utilFunction isn't being used and adding logging to the factory function shows that it's never called. I've already tried searching for jest.mock not working with relative paths and jest.mock not being called for Typescript thinking that it might be related to the mix of JS tests and TS source code or to the different module paths used in the source vs test code.
Code being tested:
// src/foo/fooModule.ts
import { utilFunction } from '../util'
export const foo = () => {
return utilFunction()
}
Test code:
// test/fooModule.test.js
const { foo } = require('../src/foo/fooModule')
jest.mock('../src/util', () => {
return { utilFunction: () => 'mocked' };
});
describe('fooModule tests', () => ...)
The jest.mock call needs to be moved above the imports:
// test/fooModule.test.js
jest.mock('../src/util', () => {
return { utilFunction: () => 'mocked' };
});
const { foo } = require('../src/foo/fooModule')
describe('fooModule tests', () => ...)
My last experience working with Jest prior to this was in a project where the tests were also written in Typescript and babel-jest was used. babel-jest includes babel-jest-hoist which hoists the jest mocks above any imports automatically, so I didn't previously have to worry about the ordering.

Error with WEB3js - NodeJs.utils in vite react ts app - How to resolve TypeError: callbackify is not a function

Hey I am playing around with web3 inside react as a client application (using vite react-ts) and trying to call web3.eth.net.getId() but this will throw me an error that callbackify is not a function I digged a little and found an old issue on the github which states that older versions of Nodejs.util (prior version 0.11) didn't have this function. So I checked the package.json where the error occurs (web3-core-requestmanager) it has "util":"^0.12.0", so callbackify should be available.
In fact when I am looking at their imports, they seem to be able to import it:
(following code is ./node_modules\web3-core-requestmanager\src\index.js
const { callbackify } = require('util');
but when they want to use it, callbackify is undefined
//RequestManager.prototype.send function
const callbackRequest = callbackify(this.provider.request.bind(this.provider));
I tried to play around with the dependencies and tried different versions of web3.js (1.7.3; 1.6.0; 1.5.1) all of them had the same util dependency (0.12.0).
My code in all this matter looks like this:
class Blockchain {
public blockchainBaseUrl: string;
public web3;
public provider;
public account: string = '';
public contract: any;
constructor() {
if (process.env.REACT_APP_BLOCKCHAIN_BASE_URL === undefined) {
throw new Error('REACT_APP_BLOCKCHAIN_BASE_URL is not defined');
}
this.provider = window.ethereum;
if (this.provider === undefined) {
throw new Error('MetaMask is not installed');
}
this.setUpInitialAccount();
this.addEthereumEventListener();
this.blockchainBaseUrl = process.env.REACT_APP_BLOCKCHAIN_BASE_URL;
this.web3 = new Web3(Web3.givenProvider || this.blockchainBaseUrl);
this.setContract();
}
async setContract() {
// error comes from the next line
const networkId = await this.web3.eth.net.getId();
this.contract = new this.web3.eth.Contract(
// #ts-ignore
Token.abi,
// #ts-ignore
Token.networks[networkId].address
);
}
}
I also was told that I should simply add a .catch() to web3.eth.net.getId() but this did nothing. Am I doing something wrong or is this a dependency problem? If anyone could point me in the right direction I would really appreciate it. Do I need to expose the util API to the browser somehow? To me, it seems that the API is simply not available.
This is should be the relevant part of my vite.config.ts:
import GlobalsPolyfills from '#esbuild-plugins/node-globals-polyfill';
import NodeModulesPolyfills from '#esbuild-plugins/node-modules-polyfill';
import { defineConfig } from 'vite';
import react from '#vitejs/plugin-react';
export default defineConfig({
plugins: [
react(),
],
optimizeDeps: {
esbuildOptions: {
plugins: [
NodeModulesPolyfills(),
GlobalsPolyfills({
process: true,
buffer: true,
}),
],
define: {
global: 'globalThis',
},
},
},
});
Here is my complete vite config
https://pastebin.com/zvgbNbhQ
Update
By now I think that I understand the issue - it seems that it is a VIte-specific problem and I need to polyfill the NodeJs.util API. I am already doing this (at least I thought). Perhaps someone can provide some guidance on what I am doing wrong with my config?
Update 2
I actually have now the util API inside the browser, but it is still giving me the same error. This is my new config:
https://pastebin.com/mreVbzUW I can even log it out:
Update 3
SO I am still facing this issue - I tried a different approach to polyfill I posted the update to the github issue https://github.com/ChainSafe/web3.js/issues/4992#issuecomment-1117894830
Had similar problem with vue3 + vite + we3
It's started from errors: process in not defined than Buffer is not defined and finally after I configure polyfill I came to callbackify is not defined
Did a lot of researches and finally solved this issue with next trick:
Rollback all polyfill configurations
Add to the head html file
<script>window.global = window;</script>
<script type="module">
import process from "process";
import { Buffer } from "buffer";
import EventEmitter from "events";
window.Buffer = Buffer;
window.process = process;
window.EventEmitter = EventEmitter;
</script>
vite.config.ts
import vue from '#vitejs/plugin-vue'
export default {
resolve: {
alias: {
process: "process/browser",
stream: "stream-browserify",
zlib: "browserify-zlib",
util: 'util'
}
},
plugins: [
vue(),
]
}
add these dependencies browserify-zlib, events, process, stream-browserify, util
Source https://github.com/vitejs/vite/issues/3817#issuecomment-864450199
Hope it will helps you

How to get localStorage working in vue testing

I am trying to test vue components.
I have a vue single file component which uses vuex. My states are stored in store.js which makes use of localStorage. However, when I run npm test I get error that reads:
WEBPACK Compiled successfully in 9416ms
MOCHA Testing...
RUNTIME EXCEPTION Exception occurred while loading your tests
ReferenceError: localStorage is not defined
Tools I am using for testing:
#vue/test-utils, expect, jsdom, jsdom-global, mocha, mocha-webpack
How I run the tests:
"test": "mocha-webpack --webpack-config node_modules/laravel-mix/setup/webpack.config.js --require tests/JavaScript/setup.js tests/JavaScript/**/*.spec.js"
A sample test, order.spec.js:
require('../../resources/assets/js/store/store');
require('../../resources/assets/js/app');
import { mount } from '#vue/test-utils';
import Order from '../../resources/assets/js/views/order/List.vue';
import expect from 'expect';
describe('Order', ()=>{
it('has alert hidden by default', () => {
let wrapper = mount(Order);
expect(wrapper.vm.alert).toBe(false);
})
})
In setup.js file I am loading jsdom like this:
require('jsdom-global')();
How do I fix this?
jsdom-global is using an old version of jsdom. jsdom has supported localStorage since 11.12.0.
To use jsdom 11.12+ with localStorage support, you can add jsdom window properties to the global scope yourself in a test setup file that runs before your tests:
/* setup.js */
const { JSDOM } = require('jsdom');
const jsdom = new JSDOM('<!doctype html><html><body></body></html>');
const { window } = jsdom;
function copyProps(src, target) {
Object.defineProperties(target, {
...Object.getOwnPropertyDescriptors(src),
...Object.getOwnPropertyDescriptors(target),
});
}
global.window = window;
global.document = window.document;
global.navigator = {
userAgent: 'node.js',
};
global.requestAnimationFrame = function (callback) {
return setTimeout(callback, 0);
};
global.cancelAnimationFrame = function (id) {
clearTimeout(id);
};
copyProps(window, global);

how can i mock a dynamically loaded, virtual module in jest?

I have a virtual javascript file in a Jest unit test with the path '/widgets/1.0.js'. I have mocked the fs module to simulate its existence.
Now i would like to dynamically load it to invoke a method 'foo()'. I thought it would be a case of using a virtual mock:
index.test.js
jest.mock('/widgets/1.0.js', () => {foo: jest.fn(() => {console.log('foo!')})}, {virtual: true});
The code which calls the mock:
index.js
let module = require('/widgets/1.0.js');
module.foo();
When i run the test:
Cannot find module '/widgets/1.0.js' from 'index.js'
at Resolver.resolveModule (node_modules/jest-resolve/build/index.js:151:17)
at processWidgets (src/index.js:115:2418)
at Object.<anonymous> (src/__tests__/index.test.js:99:73)
I think it should be possible. Any ideas?
thanks!
It appears to be a problem with the module path. This works:
index.test.js
jest.mock('1.0', () => {
return {
foo: () => {return 42;}
}
}, {virtual: true});
index.js
const module = require('1.0');
let retval = module.foo();
console.log('retval: ', retval);
If i use '/widgets/1.0' it does not. Hope it helps..

JavaScript ES6 Module OnLoad handler implementation

I have a NodeJS server application which is split to lost of ES6 modules. I am trying to create a kind of "load module handler", a function in the main module, which other modules would require to register a callback, which will be executed after the main module is fully initialized. I am using Babel (with babel-preset-es2015) to transpile ES6 modules into executable JavaScript.
To demonstrate the issue here in short, I've created 2 sample files.
File index.js (application entry, main module):
import * as js2 from "./js2.js";
let toCall = [], // this array handles callbacks from other modules
initialized = false; // flag
export function onInit (cb) { // registers cb to execute after this module is initialized
if (initialized) {
cb();
return;
}
toCall.push(cb);
}
function done () { // initialization is done - execute all registered callbacks
toCall.forEach(f => f());
}
// some important stuff here
// callback(() => {
initialized = true;
done();
// });
And the other module js2.js:
import { onInit } from "./index";
onInit(() => {
console.log("Now I can use initialized application!");
});
All seems to be OK to me, but unfortunately this doesn't work throwing the next error in the first file:
Cannot read property 'push' of undefined
Thing is, there is no toCall variable at this point, but why? Variable toCall is declared before onInit function, it must be ready to use in onInit, mustn't it? How to solve this and is my way rational enough to implement something called "module initialization callbacks"? Are there any other solutions for this?
Thank you for any help and advice.
I found a beautiful implementation for this.
It is needed to separate "onload handler" implementation to individual module. As a result of this example, there will be three files:
index.js:
import * as js2 from "./js2.js";
import { initDone } from "./init.js";
// some important stuff here
// callback(() => {
console.log(`Main module is initialized!`);
initDone();
// });
js2.js:
import { onInit } from "./init.js";
onInit(() => {
console.log("Module js2.js is initialized!");
});
init.js:
let toCall = [], // this array has to handle functions from other modules
initialized = false; // init flag
export function onInit (cb) {
if (initialized) {
cb();
return;
}
toCall.push(cb);
}
export function initDone () {
initialized = true;
toCall.forEach(f => f());
}
And the result:
Main module is initialized!
Module js2.js is initialized!

Categories