Jest Testing Electron/React Component using window.require - reactjs

I'm currently creating an Electron application that uses React to create the interface. In order to get access the fs, I have been using:
const fs = window.require('fs');
Which works fine when in an Electron window.
The issue is that when I write jest tests for any components that use the window.require('fs'), I get the following error when running the test.
TypeError: window.require is not a function
I have looked through the documentation for Jest and it seems the solution is to generate a mock of window using a manual mock (see "Mocking methods which are not implemented in JSDOM" at https://jestjs.io/docs/en/manual-mocks). However, when I tried to mock window.require by adding at the top of my test file
window.require = jest.fn();
I still get the same TypeError.
I'm very new to create Jest mocks so any help with this would be much appreciated.
My current test file (Component.test.js) looks like
window.require = jest.fn();
import React from 'react';
import renderer from 'react-test-renderer';
import Component from '../index';
describe('Testing', () => {
it('Component renders correctly', () => {
const component = renderer.create(<Component />);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});

Add this line at the beginning of your test (or in a setupFilesAfterEnv setup file):
window.require = require;
Details
electron supplies window.require but it isn't defined when running unit tests with Jest.
By default Jest provides a browser-like environment using jsdom that includes a window object.
The above line mocks window.require by setting it equal to the current value of require.

I once faced this issue and below was what solved it for me:
I had to leverage on the module react-app-rewired. this module Tweaks the webpack config(s), even for those using create-react-app(CRA) without using 'eject' and without creating a fork of the react-scripts.
all you need is to add a config-overrides.js file in the root of your project, and populate it with the snippet below:
module.exports = function override (config) {
config.target = 'electron-renderer'
return config;
}
then you proceed to your package.json and replace your start script with
"start": "react-app-rewired start" . and you are done. you can thereafter rebuild and run your test script without getting the window.require is not a function error.
I hope I have been able to help.
cheers!

Related

Jest manual mocks with create-react-app: How to use mock values in test

I have a React project set up with create-react-app. In the project there is a Configuration class that holds environment specific values. It looks like this (minimized version for illustration):
# src/Configuration.js
class Configuration {
getBackendUrl() { return "https://somewhere.com/" }
}
export default new Configuration();
As this configuration class is used widely among the components, there is a manual mock setting the proper values for testing:
# src/__mocks__/Configuration.js
class Configuration {
getBackendUrl = jest.fn(() => {return "http://localhost:3001" });
}
export default new Configuration();
The mock is loaded globally in setupTests.js:
# src/setupTests.js
jest.mock('./Configuration');
All tests, where the Configuration class is used implicitly, work correct and use the mock values.
But now I have a test where I want to assert with the actual mock values of the Configuration class. The following test code is stripped down to the core essence, in reality I'm using nock to mock the API server and then test correct handling in redux.
# src/somemodule/demo.test.js
import Configuration from '../Configuration';
describe('somemodule/demo', () => {
it('uses correct configuration values', () => {
expect(Configuration.getBackendUrl()).toEqual("http://localhost:3001")
});
});
The problem is that Configuration.getBackendUrl() is always undefined. Can I somehow achieve that the mock value is returned?
I could fix it by manually overwriting the mock in the beforeEach test suite method like this, but I'd prefer to avoid duplication: Configuration.getBackendUrl.mockImplementation(() => 'http://localhost:3001').
Thanks for any help!
EDIT: added the actual used code of the mock.
Apologies for initially asking the question with not reflecting the real used code.
In fact the mock of Configuration used the following code (the question is now adjusted with it):
# src/__mocks__/Configuration.js
class Configuration {
getBackendUrl = jest.fn(() => {return "http://localhost:3001" });
}
export default new Configuration();
This will always return a MockInstance per method and thus the undefined in the test.
If the mock is fixed to not use the jest.fn calls, then it works:
# src/__mocks__/Configuration.js
class Configuration {
getBackendUrl(){ return "http://localhost:3001" };
}
export default new Configuration();
Once again sorry and maybe it is helpful for someone.

Cypress test fails to run when importing from source code of CRA+TS app

One of my Cypress tests fails to run when it tries to import from a file in the source code of a create-react-app src directory.
The test looks like:
// cypress/integration/this-fails.js
import { MY_CONSTANT } from '../../src/constants';
describe('Cypress', () => {
...
})
The imported source file looks like:
// src/constants.ts
export const MY_CONSTANT = 'foo';
The Cypress test failure is caused by a Jest test file in the source directory:
ERROR in /my-app/src/App.test.tsx(5,1)
Cannot find name 'test'. Do you need to install type definitions for a test runner? Try`npm i #types/jest`or`npm i #types/mocha`.
The Jest type definitions are installed. Additionally, to no avail, I have tried to exclude the problematic Jest test in the Cypress tsconfig.
// cypress/tsconfig.json
{
...
"exclude": [
"../src/App.test.tsx"
],
...
}
Here is a minimal repo that reproduces my problem.
Lastly, to clarify why I am importing things into Cypress tests from the source directory — the imported variable is intended to be a DOM selector or a function that returns a DOM selector so that selectors are not hardcoded in the tests.
I'm not sure why the message is TypeScript emitted no output for /my-app/src/constants.ts, this seems to indicate that the file is readable and typescript attempts to parse it, and does not recognize the syntax.
However my guess is that the code of the test is running in a browser process and can't access files outside of it's folder.
If constant.ts is in cypress/fixtures it works, so one easy way is to add a script to copy the file. A script called "precypress" will be automatically run when the "cypress" script is invoked.
This is kind of 90% there - you don't get hot-module reload when constants.ts changes.
package.json
"scripts": {
...
"precypress": "copyfiles ./src/constants.ts ./cypress/fixtures",
"cypress": "cypress open"
},
It also works with functions and handles typing,
test
import { MY_CONSTANT, getMyConstant } from '../fixtures/src/constants';
describe('Cypress', () => {
it('is working', () => {
cy.visit('/')
alert(MY_CONSTANT);
alert(getMyConstant());
expect(true).to.equal(true)
})
})
constant.ts
export const MY_CONSTANT: Number = 10;
export const getMyConstant: Function = () => 20;

How does React Lazy fetch components?

I recently read about React Lazy and how it "loads" the components during run time when they are required to be rendered. I assume that "loading" here means fetching the component from the server and then rendering it.
So my question is, how does React manage this fetching of components? How does it know the exact path from where to fetch this component (given that our code will mention the relative path but fetching will require complete server path)? Does it depend on Webpack for this?
Let's look into the React code. React.lazy is defined as follows.
export function lazy<T, R>(ctor: () => Thenable<T, R>): LazyComponent<T> {
let lazyType = {
$$typeof: REACT_LAZY_TYPE,
_ctor: ctor,
// React uses these fields to store the result.
_status: -1,
_result: null,
};
if (__DEV__) {
// ... additional code only in development mode
}
return lazyType;
}
As you can see, React.lazy requires a Promise which resolves to a module with a default export containing a React component (freely cited by React Docs). This also means that not React resolves the file, but import() does. import() works as documented in the MDN.
The async import() is a new function in ES6 which is not available in all browsers but can be polyfilled by Webpack and Babel/Typescript/others.
What you often see is code like the following, which automatically splits the imported file away by Webpack.
import(/* webpackChunkName: "xyz" */ './component/XYZ')
This creates a new javascript xyz.js next to your bundle script.
If you don't use Webpack, you need to create those files by yourself. Webpack just reduces the work required from you. So you don't absolutely depend on Webpack. This approach might look like the following:
// ./component/xyz.js
export default function() { return <div>Component</div> }
// ./main.js
const OtherComponent = React.lazy(() => import('./component/xyz.js'));
export default function() { return <div>Component</div> }
And the file structure:
| public
|---| main.js
|---| component
|---| --- | main.js
As you see, no webpack required. It just makes your life easier.

How to use react-native-i18n in detox[react-native]

I want to test the alert message in detox,and the message use i18n.
const i18n = require("react-native-i18n");
describe("Example", () => {
beforeEach(async () => {
await device.reloadReactNative();
});
it("should show hello screen after tap", async () => {
await element(by.id("btnLogin")).tap();
I18n.t(LocaleKeys.errorMsg_invalidUsername);
await expect(element(by.text(I18n.t(LocaleKeys.errorMsg_invalidUsername)))).toBeVisible();
// await expect(element(by.text("Please input the email and password."))).toBeVisible();
});
});
Run test and get the following error.
Test suite failed to run
/Users/leogeng/Desktop/studentREP/student-app/node_modules/react-native-i18n/index.js:14
export const getLanguages = () => RNI18n.getLanguages();
^^^^^^
SyntaxError: Unexpected token export
at ScriptTransformer._transformAndBuildScript (../node_modules/jest-runtime/build/script_transformer.js:305:17)
at Object.<anonymous> (firstTest.spec.js:1:114)
at Generator.next (<anonymous>)
Then I add the following code for jest:
{
"preset": "react-native",
"transformIgnorePatterns": [
"/node_modules/(?!(react-native(.*)?/|native-base(.*)?/|react-navigation/))"
]
}
and get error again:
Validation Error:
Module <rootDir>/node_modules/react-native/jest/setup.js in the setupFiles option was not found.
Actually i confirm 'setup,js' exist in node_modules/react-native/jest.
I do not know why the error happens, anybody can help me?
Thanks
Most likely it's because you're using an old version of node, try to update and see if it solves the issue. Also, it's completely unrelated to Jest and you should probably revert your attempts to modify Jest settings if you don't have any issues with Jest unit tests; in anyway, it will not fix the detox issues.
In case you have some requirement or reason which forces you to keep node at a specific old version, you can bypass it by performing the test differently: have a demo screen only for the e2e tests (or even create a whole demo project just for e2e), in the demo screen you can have a button which performs what you need with i18n (changing locale or whatever), and in the detox test you tap this "demo" button before testing what you actually want.
I've had the same problem. I resolve it by importing i18n-js instead of react-native-i18n.
Because react-native-i18n is not a plain javascript framework, Detox can't import it.
But react-native-i18n is using i18n-js, so you can access your translations without any problem
const I18n = require('i18n-js')
// and then you can use it for your tests
...
await element(by.text( I18n.t('hello') )).tap()

Exclude node_module/package from production build webpack

I am working on a project that uses "webpack": "^2.4.1",, it is a ReactJS project, I have installed the module airbnb/prop-types-exact, I am using this package for development purposes, where I would not want a user of a component I wrote to pass non-existing properties to that component.
I would like to remove this package when I build the app for production. I am using the Webpack Bundle Initializer to see the bundle size of airbnb/prop-types-exact, it is not that big, but I would like to have it removed from the production build, Is this achievable? With the webpack version that I am using or with a latter one?
I would appreciate any resources or ideas regarding this, thanks.
Following through an example from this Blog by Mark
And more references on these plugins:
IgnorePlugin and DefinePlugin
I have used the plugins as he did, which are IgnorePlugin and UglifyJsPlugin and then in the component where I am using the airbnb/prop-types-exact package, I am doing a check on which environment I am in like..
let exactProps ;
if (process.env.NODE_ENV === "development") {
exactProps = require("prop-types-exact");
}
And depending on whether the exactProps has a value, meaning the require function has ran, and ,meaning the exactProps has the function from the prop-types-exact package,
I am wrapping the my prop types with this function, .eg.
const propTypes = {
someProp: PropTypes.iRequired
}
if (exactProps && typeof exactProps === "function") {
MyComponent.propTypes = exactProps(propTypes);
} else {
MyComponent.propTypes = propTypes;
}
And finally I export the MyComponent component
export MyComponent
I am planning to move the wrapping of the component's prop types into a generic module, so that it is re-usable

Resources