ReactJS - Importing store breaks Jest - javascript

I had a file with one function that creates an axiosInstance, it used to look like this:
import Axios from 'axios';
import getToken from '#/api/auth-token';
const [apiUrl] = [window.variables.apiUrl];
export const createApiInstance = () => {
return Axios.create({
baseURL: `${apiUrl}`,
headers: {
Authorization: `Bearer ${getToken()}`,
},
});
};
Something we wanted to implement was notifying the user when their JWT token expires (so they won't unwittingly continue using the app while all of their requests are returning 401s). My solution turned this file into this:
import Axios from 'axios';
import getToken from '#/api/auth-token';
import store from '#/main.jsx';
import { userActionCreators } from '#/store/action-creators';
const [apiUrl] = [window.variables.apiUrl];
export const createApiInstance = (urlExtension = '') => {
const axiosInstance = Axios.create({
baseURL: `${apiUrl + urlExtension}`,
headers: {
Authorization: `Bearer ${getToken()}`,
},
});
axiosInstance.interceptors.response.use(response => {
if (response.status === 401)
store.dispatch(userActionCreators.setUserTokenExpired());
return response;
});
return axiosInstance;
};
This worked very well, as now all of the responses from the API are being checked for a 401 Unauthorized status and dispatching an action so the app can respond to it.
Jest doesn't like the import store, so 95% of the tests in the app fail when the store is imported here. I'm certain it is the importing of the store because every test passes when it is commented it.
I've had absolutely no luck getting anything to work.
I've tried updating setting jest and babel-jest to the same versions, setting react, react-dom, and react-test-renderer to the same versions. I've looked into configuring moduleNameMapper to mock the store in the jest config in package.json but I'm not sure how to make that work. I'm starting to consider taking a completely different approach to this problem such as applying middleware to check for 401 responses instead, but I'm worried I'll end up running into the same issue after a bunch of work.
The problem with mocking the store in the test files themselves is that there's hundreds of test files in this large application, so literally anything besides adding mocking to each individual file is the solution I'm looking for.

If anyone else is having this problem, this occurred because I was exporting the store from the same file as my ReactDOM.render. Apparently you can export from this file but as soon as you try to import what is exported somewhere else it will catch it and break tests. The solution is to create and export the store from a different file.

Make sure you have a .babelrc file, as jest doesn't understand the context of babel and JSX files otherwise. Related Stack Qusetion
If that doesn't quite do the trick can you update with the main.jsx code and let me know then i'll update

Related

I am getting error axios.default.create is not function with webpack and react-cra

We have created a custom javascript library called Domain and we are using Axios to hit API calls inside that library. we gave app-domain name in package.json and axios as dependencies also.
import axios from "axios";
export class Domain {
axiosInst: any;
constructor() {
console.log('axios -->', axios);
this.axiosInst = axios.create({
baseURL: config.baseUrl,
headers: {
'Content-Type': 'application/json'
}
});
}
}
Now, If we use this custom library in our node application (we are adding app-domain in node package.json dependencies) we are able to use this Domain class properly and can hit the API's also.
But if we use the same thing in our react (CRA) application we are getting an below error.
Uncaught TypeError: axios_1.default.create is not a function
After some debugging we found that in Node application we are getting actual axios object but during react application we are getting
console.log('axios -->', axios);
/static/media/axios.d3b7d28587da77444af0.cjs
So Webpack is considering Axios as assets and creates a file for that which we don't need.
Does anyone have any idea how to solve this issue?

Spying On/Mocking Import of an Import

I'm writing unit tests using vitest on a VueJS application.
As part of our application, we have a collection of API wrapper services, e.g. users.js which wraps our relevant API calls to retrieve user information:
import client from './client'
const getUsers = () => {
return client.get(...)
}
export default {
getUsers
}
Each of these services utilise a common client.js which in turn uses axios to do the REST calls & interceptor management.
For our units tests, I want to check that the relevant url is called, so want to spy on, or mock, client.
I have followed various examples and posts, but struggling to work out how I mock an import (client) of an import (users.js).
The closest I've been able to get (based on these posts - 1, 2) is:
import { expect, vi } from 'vitest'
import * as client from '<path/to/client.js>'
import UsersAPI from '<path/to/users.js>'
describe('Users API', () => {
beforeEach(() => {
const spy = vi.spyOn(client, 'default') // mock a named export
expect(spy).toHaveBeenCalled() // client is called at the top of users.js
})
test('Users API.getUsers', () => {
UsersAPI.getUsers()
expect(spy).toHaveBeenCalled()
})
})
but it's tripping on:
❯ async frontend/src/api/client.js:3:31
2| import store from '#/store'
3|
4| const client = axios.create({
| ^
5| headers: {
6| 'Content-Type': 'application/json'
where it's still trying to load the real client.js file.
I can't seem to mock client explicitly because the import statements run first, and so client is imported inside users.js before I can modify/intercept it. My attempt at the mocking was as follows (placed between the imports and the describe):
vi.mock('client', () => {
return {
default: {
get: vi.fn()
}
}
})
Mocking a module
vi.mock()'s path argument needs to resolve to the same file that the module under test is using. If users.js imports <root>/src/client.js, vi.mock()'s path argument needs to match:
// users.js
import client from './client' // => resolves to path/to/client.js
// users.spec.js
vi.mock('../../client.js') // => resolves to path/to/client.js
It often helps to use path aliases here.
Spying/mocking a function
To spy on or mock a function of the mocked module, do the following in test():
Dynamically import the module, which gets the mocked module.
Mock the function off of the mocked module reference, optionally returning a mock value. Since client.get() returns axios.get(), which returns a Promise, it makes sense to use mockResolvedValue() to mock the returned data.
// users.spec.js
import { describe, test, expect, vi } from 'vitest'
import UsersAPI from '#/users.js'
vi.mock('#/client')
describe('Users API', () => {
test('Users API.getUsers', async () => {
1️⃣
const client = await import('#/client')
2️⃣
const response = { data: [{ id: 1, name: 'john doe' }] }
client.default.get = vi.fn().mockResolvedValue(response)
const users = await UsersAPI.getUsers()
expect(client.default.get).toHaveBeenCalled()
expect(users).toEqual(response)
})
})
demo
Late to the party but just in case anyone else is facing the same issue.
I solved it by importing the module dependency in the test file and mocking the whole module first, then just the methods I needed.
import { client } from 'client';
vi.mock('client', () => {
const client = vi.fn();
client.get = vi.fn();
return { client }
});
Then in those tests calling client.get() behind the scenes as a dependency, just add
client.get.mockResolvedValue({fakeResponse: []});
and the mocked function will be called instead of the real implementation.
If you are using a default export, look at the vitest docs since you need to provide a default key.
If mocking a module with a default export, you'll need to provide a default key within the returned factory function object. This is an ES modules specific caveat, therefore jest documentation may differ as jest uses commonJS modules.
I've accepted the above answer, as that did address my initial question, but also wanted to include this additional step I required.
In my use case, I need to mock an entire module import, as I had a cascading set of imports on API files that in turn, imported more and more dependencies themselves.
To cut this, I found this in the vuex documentation about mocking actions:
https://vuex.vuejs.org/guide/testing.html#testing-actions
which details the use of webpack and inject-loader to substitute an entire module with a mock, preventing the source file loading at all.

Installing Plaiceholder in Next.js / Webpack 5 causes: Module not found: Can't resolve 'child_process'

I'm building on Next.js app and when I install / import Plaiceholder (for generating placeholder images), I get the following error: Module not found: Can't resolve 'child_process'
Node v14.18.0
Next.js v11.1.2
Plaiceholder v2.2.0
Sharp v0.29.2
I understand this error message to mean that webpack5 is trying to bundle node packages that aren't available to the client. But I'm not clear why it is doing this. I haven't customized any of the webpack configs, and I can't find any mention of this issue in the Plaiceholder docs. How do I fix it?
NOTE: I want the base64 data URL to get created during the build, so that it available as soon as the page loads (not fetched asynchronously at run time).
Here's my next.config.js
module.exports = {
reactStrictMode: true,
};
My package.json only has scripts, dependencies, and devDependencies (nothing to change module resolution)
In case it's relevant, here's a simplified example using Plaiceholder:
import Image from "next/image";
import { getPlaiceholder } from "plaiceholder";
import React, { useState } from "react";
...
const { base64 } = await getPlaiceholder(imgUrl);
...
return (<Image
src={imgUrl}
placeholder="blur"
blurDataURL={base64}
/>);
It seems like plaiceholder is not suitable for client-side rendering. I believe that package is for the Node.js environment. That's why you get this error when you try to render your component on the client side.
To solve this problem, you need to move import { getPlaiceholder } from 'plaiceholder' to the NextJS API section. Then you can call that API with your URL data in the body. Then get the base64.
/api/getBase64.js
import { getPlaiceholder } from "plaiceholder";
export default async (req, res) => {
const { body } = req;
const { url } = body;
const { base64 } = getPlaiceholder(url);
res.status(200).send(base64);
};
/component.js
import Image from "next/image";
import React, { useState, useEffect } from "react";
const [base64, setBase64] = useState()
useEffect(() => {
(async () => {
const _base64 = await fetch.post('/api/getBase64', {url: imgUrl}); // wrote for demonstration
setBase64(_base64);
})()
})
return (<Image
src={imgUrl}
placeholder="blur"
blurDataURL={base64}
/>);
I know blurDataURL will be undefined until you fetch the data but this is the way how you can use plaiceholder library to manage your images. It should be imported only for the NodeJS environment. If you do not like this approach, you can try to find another library that also works for the browser environment (client)
UPDATED according to the comment:
If you want to generate this base64 at build time, you can use getStaticProps in the pages that use this Image component. NextJS is smart enough to understand which libraries are used in the client-side or server-side. So you can do this:
import { getPlaiceholder } from "plaiceholder"; // place it at the root of file. This will not be bundled inside of client-side code
export async function getStaticProps(context) {
const { base64 } = await getPlaiceholder(imgUrl);
return {
props: { base64 }, // will be passed to the page component as props
}
}
This way, by using getStaticProps, the page will be created at build time. You can get the base64 prop inside of the page that uses the image component and pass that prop to blurDataURL. Also, you can use this approach with getServerSideProps too.
This is from NextJS website:
Note: You can import modules in top-level scope for use in
getServerSideProps. Imports used in getServerSideProps will not be
bundled for the client-side.
https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering
It's necessary to Install plugin for Next Js dependency and configure next config based on Plaiceholder Docs for using getPlaiceholder() function in getStaticProps like the answer by #oakar.
npm i #plaiceholder/next
const { withPlaiceholder } = require("#plaiceholder/next");
module.exports = withPlaiceholder({
// your Next.js config
});

How to best import "server-only" code in Next.js?

In the getServerSideProps function of my index page, I'd like to use a function foo, imported from another local file, which is dependent on a certain Node library.
Said library can't be run in the browser, as it depends on "server-only" modules such as fs or request.
I've been using the following pattern, but would like to optimize it. Defining foo as mutable in order to have it be in scope is clunky and seems avoidable.
let foo;
if (typeof window === "undefined") {
foo = require("../clients/foo");
}
export default function Index({data}) {
...
}
export async function getServerSideProps() {
return {
props: {data: await foo()},
}
}
What would be the best practice here? Is it somehow possible to leverage ES6's dynamic import function? What about dynamically importing within getServerSideProps?
I'm using Next.js version 9.3.6.
Thanks.
UPDATE:
It seems as if Next.js's own dynamic import solution is the answer to this. I'm still testing it and will update this post accordingly, when done. The docs seem quite confusing to me as they mentionn disabling imports for SSR, but not vice versa.
https://nextjs.org/docs/advanced-features/dynamic-import
When using getServerSideProps/getStaticProps, Next.js will automatically delete any code inside those functions, and imports used exclusively by them from the client bundle. There's no risk of running server code on the browser.
However, there are a couple of considerations to take in order to ensure the code elimination works as intended.
Don't use imports meant for the server-side inside client-side code (like React components).
Ensure you don't have unused imports in those files. Next.js won't be able to tell if an import is only meant for the server, and will include it in both the server and client bundles.
You can use the Next.js Code Elimination tool to verify what gets bundled for the client-side. You'll notice that getServerSideProps/getStaticProps gets removed as do the imports used by it.
Outside of getServerSideProps/getStaticProps, I found 2 fairly similar solutions.
Rely on dead code elimination
In next.config.js:
config.plugins.push(
new webpack.DefinePlugin({
'process.env.RUNTIME_ENV': JSON.stringify(isServer ? 'server' : 'browser'),
}),
);
export const addBreadcrumb = (...params: AddBreadcrumbParams) => {
if (process.env.RUNTIME_ENV === 'server') {
return import('./sentryServer').then(({ addBreadcrumb }) => addBreadcrumb(...params));
}
return SentryBrowser.addBreadcrumb(...params);
};
Note that some for reason I don't understand, dead code elimination does not work well if you use async await, or if you use a variable to store the result of process.env.RUNTIME_ENV === 'server'. I created a discussion in nextjs github.
Tell webpack to ignore it
In next.config.js
if (!isServer) {
config.plugins.push(
new webpack.IgnorePlugin({
resourceRegExp: /sentryServer$/,
}),
);
}
In that case you need to make sure you will never import this file in the client otherwise you would get an error at runtime.
You can import the third party library or a serverside file inside getServerSideProps or getInitialProps since these functions run on server.
In my case I am using winston logger which runs on server only so importing the config file only on server like this
export async function getServerSideProps (){
const logger = await import('../logger');
logger.info(`Info Log ->> ${JSON.stringify(err)}`);
}
You can also import library/file which has default export like this
export async function getServerSideProps(context) {
const moment = (await import('moment')).default(); //default method is to access default export
return {
date: moment.format('dddd D MMMM YYYY'),
}
}

How to unit test a function that depends on Firebase reference?

I want to unit test a function in my application that calls and updates a Firebase reference. The problem I am facing is that when I try to run the test and import the file that contains the function, I get the following error SyntaxError: Unexpected token u in JSON at position 0 which points to the line in my function file that imports my reference to Firebase.
I am using Webpack and Babel on this project, so I tried setting a resolve.alias in the webpack.config file which worked when the application was running, but did not work when I ran npm test. At this point I'm at a loss as to how I can mock out the Firebase reference so I can test the other functions of the function. Here's some sample code:
constants/index.js
import firebase from 'firebase';
const firebaseConfig = JSON.parse(unescape(`${config.FIREBASE_CONFIG}`));
const mainApp = firebase.initializeApp(firebaseConfig);
export const ref = mainApp.database().ref();
actions/settings.js
import * as actions from './';
import { ref } from '../constants';
export const updateSetting = (e, value) =>
(dispatch) => {
ref.child('setting')
.set(value)
.then(() => {
dispatch(actions.confirmFBSave());
})
.catch((err) => {
dispatch(actions.failFBSave({ text: err.code }));
});
};
What testing framework are you using Qunit, jasmine, mocha? With AngularFire and Angular it is much easier because you can pass a mock class in at DI, but I have also tested non angular js for server side. Then I used Sinon to get mock initializeApp and return a Mock firebase app.
You could also try https://github.com/thlorenz/proxyquire I didn't need to go that far, but I have seen it suggested. That way you are essentially intercepting the import and swap it out for something you can test with.

Categories

Resources