Writing Unit Test Cases for Preact - jestjs

I am very new to Preact and I have to write unit test cases for an application in Preact. I can find that jest and enzyme can be used for the same but I run into errors every time. Can anyone suggest some blog or tutorial on how to write unit test cases on Preact?

Update
Now is preact-enzyme-adapter available which makes it possible to run preact testing with Enzyme. I haven't tested it yet, but I'd recommend it a try since Enzyme got a really nice toolbelt and already is widely used.
Original answer
This preact boilerplate project has a setup with unit tests included. They're using jest to run the tests.
Below is, as a I understand, the relevant parts to get up and running.
package.json
"jest": {
"setupFiles": [
"./test/setup.js"
],
"testURL": "http://localhost:8080",
"moduleFileExtensions": [
"js",
"jsx"
],
"moduleDirectories": [
"node_modules"
],
"moduleNameMapper": {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
"\\.(css|less)$": "identity-obj-proxy",
"^react$": "preact-compat",
"^react-dom$": "preact-compat"
},
"collectCoverageFrom": [
"src/**/*.{js,jsx}"
]
},
test/setup.js:
import 'regenerator-runtime/runtime';
import chai from 'chai';
import assertJsx, { options } from 'preact-jsx-chai';
// when checking VDOM assertions, don't compare functions, just nodes and attributes:
options.functions = false;
// activate the JSX assertion extension:
chai.use(assertJsx);
global.sleep = ms => new Promise( resolve => setTimeout(resolve, ms) );
home/index.test.js
import { h } from 'preact';
import { expect } from 'chai';
import Home from '../../../src/components/home';
describe('components/home', () => {
it('should show the home text', () => {
const home = <Home/>;
expect(home).to.contain(<h1>Home</h1>);
expect(home).to.contain('Home component');
});
});

Have a look at this template project with preact-render-spy and it doesn't have dependencies on react nor enzyme. As plus TypeScript is also configured.

Related

Node.js: How to import test files in custom test runner

I'm trying to create my own custom testing framework for learning purpose. Test files are written in following way
import { somemethod } from './some/module'
test(/I click on a button)/, () => {
browser.get("someSelector").should("have.text",somemethod());
});
I user require(file) to load test files. But it throw error SyntaxError: Unexpected token {
for import statement in test file. I'm using node js version 11.15.
If I switch to node v13.14 and define "type": "module" in my package.json then it doesn't let me use require(file) to load a test file or any module in my package.
How can I import tests files considering the user may be importing the modules using import or require?
This answer is very empirical...
Considering that it works using canonical commonjs approach you can try to debug it with newer version of NODE (currently I would use 14). For it, I would suggest you to use a node version manager like NVM so you can switch between node version easily and test that accordling seeing differences between various node installations.
Make a minimal project with npm init with a single dependency, save your index with the .mjs extension and try an import the above dependency. If you are be able to import that dependency with that minimal environment you can blame either your previous node or your configuration or both of them.
At the moment you should only create a small 2 files project to reproduce the problem. It seems your current node does not consider the "type": "module" configuration and runs everything in its classic way.
Regarding your comments....
As far as I know import can be used even in your code, not just at the beginning:
(async () => {
if (somethingIsTrue) {
// import module for side effects
await import('/modules/my-module.js');
}
})();
from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
Additionally you can try Webpack with a configuration like:
// webpack.config.js
const nodeExternals = require('webpack-node-externals');
module.exports = {
mode: 'production',
target: 'node',
externals: [nodeExternals()],
entry: {
'build/output': './src/index.js'
},
output: {
path: __dirname,
filename: '[name].bundle.js',
libraryTarget: 'commonjs2'
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
['env', {
'targets': {
'node': 'current'
}
}]
]
}
}
}]
}
};
With NodeExternals you don't put your node dependencies in the bundle but only your own code. You refer to node_modules for the rest. You might not want that.

Unit test for aws lambda using jest

const invokeApi = require("/opt/nodejs/kiwiCall");
const decrypt = require("/opt/nodejs/encryption");
const cors = require("/opt/nodejs/cors");
When I am testing my index.js file by manual mocking these dependencies in mocks directory as follows:
__mocks__
|_invokeApi
|_decrypt
|_cors
it says
FAIL ./index.test.js
● Test suite failed to run
Cannot find module '/opt/nodejs/kiwiCall' from 'index.js'
However, Jest was able to find:
'../../../../lambdas/Flights/Locations/index.js'
You might want to include a file extension in your import, or update your 'moduleFileExtensions', which is currently ['js', 'json', 'jsx', 'ts', 'tsx', 'node'].
See https://jestjs.io/docs/en/configuration#modulefileextensions-array-string
1 | "use strict";
2 |
> 3 | const invokeApi = require("/opt/nodejs/kiwiCall");
Wanted to know how can I mock the dependencies of AWS lambda in inedx.test.js file
In your package.json or jest.config you could add a moduleNameMapper for that directory.
"jest": {
"moduleNameMapper": {
"/opt/nodejs/(.*)": "<rootDir>/../nodejs/$1"
},
},
So I managed to figure out something based on my repository.
I'm using the moduleNameMapper to map the absolute path to another location in my repository to where I have the layer stored.
Eg.
moduleNameMapper: {'^/opt/config/config': '<rootDir>/src/layers/layers-core/config/config'}
In your case you could use a regex expression to match /opt/nodejs/ and map it elsewhere. Hope that helped.
EDIT:
I completely changed my approach and used babel-plugin-module-resolver with babel-rewire. I did this because the above method was incompatible with rewire. It's quite easy setup and you just need to setup a babel alias within .babelrc.
eg.
{
"plugins": [
["rewire"],
["babel-plugin-module-resolver", {
"alias": {
"/opt/config/config": "./src/layers/layers-core/config/config",
"/opt/utils/util-logger": "./src/layers/layers-core/utils/util-logger",
"/opt/slack": "./src/layers/layers-slack/slack"
}
}]
]
}
Combine this with IDE jsconfig.json path alias and you get full IDE support.
{
"compilerOptions": {
"module": "commonjs",
"target": "es2018",
"baseUrl": "./",
"paths": {
"/opt/config/config": ["src/layers/layers-core/config/config"],
"/opt/utils/util-logger": ["src/layers/layers-core/utils/util-logger"],
"/opt/slack/*": ["src/layers/layers-slack/slack/*"],
}
},
"exclude": ["node_modules", "dist"]
}
You can then reference your layers with jest.doMock('/opt/config/config', mockConfig);
EDIT 2:
Found a way to get Jest to mock it. Just slip {virtual: true} into the mock!
jest.doMock('/opt/config/config', mockConfig, {virtual: true});
I have pretty much the same issue. I have defined a layer which contains common code that's shared between other functions in my project. My project structure looks something like this:
project/
functions/
function1/
app.js
function2/
app.js
shared/
shared.js
I import my shared library like this:
const { doSomething } = require('/opt/shared');
exports.handler = async (event) => {
const result = await doSomething();
// etc...
return {statusCode: 200};
}
This works when I deploy to AWS Lambda because the /opt/shared exists and it can be referenced correctly. It also works if I run this on my machine using sam local invoke Function1 because it's running in a container, which makes /opt/shared available to the code.
However, I'm struggling to work out how I can mock this dependency in a unit test. If I simply do this: jest.mock('/opt/shared'), I'm getting: Cannot find module '/opt/shared' from app.test.js
You can use the modulePaths option, from this post.
Documentation
jest.config.js
"jest": {
"modulePaths": [
"<rootDir>/src/layers/base/nodejs/node_modules/"
]
}
You can dynamically create this array by scanning a directory
const testFolder = './functions/';
const fs = require('fs');
const modulePaths = fs.readdirSync(testFolder)
.reduce((modulePaths, dirName) => {
modulePaths.push(`functions/${dirName}/dependencies/nodejs/node_modules/`);
return modulePaths;
}, []);

Cannot test jest with monaco editor - unexpected token

Running jest on the application fails with:
Details:
/home/**/node_modules/monaco-editor/esm/vs/editor/editor.api.js:5
import { EDITOR_DEFAULTS } from './common/config/editorOptions.js';
^
SyntaxError: Unexpected token {
> 1 | import * as monaco from "monaco-editor/esm/vs/editor/editor.api.js";
| ^
2 |
3 | /**
4 | * Get create function for the editor.
at ScriptTransformer._transformAndBuildScript (node_modules/#jest/transform/build/ScriptTransformer.js:537:17)
at ScriptTransformer.transform (node_modules/#jest/transform/build/ScriptTransformer.js:579:25)
at Object.<anonymous> (src/utils/editor-actions.js:1:1)
Application has installed packages for jest and babel-jest.
Babel config:
const presets = [
[
"#babel/env",
{
targets: {
edge: "17",
firefox: "60",
chrome: "67",
safari: "11.1"
},
useBuiltIns: "usage",
corejs: 3,
}
],
"#babel/preset-react"
];
const plugins = [
"#babel/plugin-proposal-object-rest-spread",
"#babel/plugin-proposal-class-properties",
"babel-plugin-styled-components"
];
module.exports = { presets, plugins };
The import statement as suggested in the docs for lazy loading modules from monaco leads to the esm folder, which jest is not familiar with.
import * as monaco from "monaco-editor/esm/vs/editor/editor.api.js";
By default, babel-jest would not transform node_modules and hence anything referencing the monaco-editor would error out. A possible solution is to include monaco-editor package into the compilation step by transformIgnorePatterns as mentioned in the docs
Add these to the jest config:
{
"transformIgnorePatterns": [
"node_modules\/(?!(monaco-editor)\/)"
]
}
PS: If you are using jest-dom, it might start complaining on not implmenting certain features from monaco-editor, you can mock it out with:
jest.mock("monaco-editor/esm/vs/editor/editor.api.js");
The only workaround that helped me in that issue (from here):
In folder __mocks__ create file react-monaco-editor.js with content:
import * as React from 'react';
export default function MonacoEditor() {
return <div />;
}
I have the same issue with Jest and Monaco and to make the tests pass I had to:
Add a moduleNameMapper for monaco-editor: #133 (comment)
Configure a setupTests.js like explained here: stackOverflow
I'm using:
"jest": "^24.9.0"
"babel-jest": "24.9",
"monaco-editor": "^0.20.0"
"react-monaco-editor": "0.33.0",
"monaco-editor-webpack-plugin": "^1.8.2",

Jest Is Not Transpiling ES6 Modules In Test (SyntaxError: Unexpected token export)

I'm at a boiling point with getting Jest to understand ES6 modules import/export syntax and it is hindering my project development progress. My project structure is the following:
root module
org-domain (ES6)
org-services (ES6)
react-ui-module (React via CRA2)
org-services has a local path dependency on org-domain in its package json:
// in org-services package.json
"dependencies": {
"#org/domain": "file:../org-domain",
},
My .babelrc in org-services is the following:
{
"env": {
"test": {
"presets": ["#babel/preset-flow", "#babel/preset-env"]
"plugins": ["#babel/plugin-transform-modules-commonjs"]
}
},
"presets": [
"#babel/preset-flow",
["#babel/preset-env", {
"targets": {
"esmodules": true
}
}]
],
"plugins": [
["module-resolver", {
"root": ["./node_modules/#org/domain"],
"alias": {
"#org/constants": "./node_modules/#org/domain/src/constants",
"#org/contracts": "./node_modules/#org/domain/src/request-contracts"
}
}]
]
}
I do not know if the problem is due to how I am including my dependencies so I'm going to add the finer details of anything related to my import/export of these modules for the sake of clarity.
In the implementation files of org-services I am importing org-domain using npm scoped syntax like so: import ... from '#org/domain
Here are some observations I have:
In local development, when I try to reference click #org/domain, instead of being directed to org-services/node_modules/#org/domain I get redirected to the actual relative directory location which is root/org-services. I believe Jest ignores node_modules (correct me if I am wrong) but in my jest.config.js for org-services, I have:
collectCoverage: true,
coverageDirectory: 'coverage',
coveragePathIgnorePatterns: [
'/node_modules/'
],
moduleDirectories: [
'src',
'node_modules'
],
moduleFileExtensions: [
'js'
],
transform: {
'^.+\\.js$': 'babel-jest'
},
transformIgnorePatterns: [
'node_modules/(?!#org/domain)$'
]
To my understanding, everything should just work right now with all the configuration I have with respect to setting the plugin #babel/plugin-transform-modules-commonjs in test (within .babelrc) and including the '^.+\\.js$': 'babel-jest' instruction under the transform key in jest.config.js located under org-services -- but it does not.
I have tried every single thing I could find online with respect to this issue with no success. I have not gotten anywhere since and my patience is lost with this testing framework and the lack of support for ES6. It should not be this hard, so clearly I am doing something wrong here. Please advise.
Update 1
Found another SO post that is a mirror of this situation I am in.
Jest fails to transpile import from npm linked module
Unfortunately, the provided solution does not work for my case.
After upgrading from #babel/xyz: 7.0.0 to 7.1.2 I started getting an error regarding "import"
Jest encountered an unexpected token
<snip>
Details:
C:\....\SstcStrategy.test.js:2
import sequelizeFixtures from 'sequelize-fixtures';
^^^^^^
SyntaxError: Unexpected token import
at ScriptTransformer._transformAndBuildScript (node_modules/jest-runtime/build/script_transformer.js:403:17)
To fix this I had to add #babel/plugin-transform-modules-commonjs as you mention in your question.
My babel.config.js now looks like this:
module.exports = {
presets: [
[
'#babel/preset-env',
{
targets: {
node: '8.10',
},
debug: false,
},
],
],
ignore: ['node_modules'],
plugins: [
'#babel/plugin-transform-runtime',
'#babel/plugin-transform-modules-commonjs',
// Stage 2
['#babel/plugin-proposal-decorators', { legacy: true }],
'#babel/plugin-proposal-function-sent',
'#babel/plugin-proposal-export-namespace-from',
'#babel/plugin-proposal-numeric-separator',
'#babel/plugin-proposal-throw-expressions',
// Stage 3
'#babel/plugin-syntax-dynamic-import',
'#babel/plugin-syntax-import-meta',
['#babel/plugin-proposal-class-properties', { loose: false }],
'#babel/plugin-proposal-json-strings',
],
};
Also BTW you don't need to define babel-jest transform in jest.config.js as this is the default setting.
Hope this helps

Webpack import from files that use module.exports

I have React app and a file where I want to store things related to api.
const proxy = require('http-proxy-middleware');
const path = require('path');
//.....
const targetApi = (objectWithUrlEntries) => {
Object.keys(objectWithUrlEntries).forEach((key) => {
objectWithUrlEntries[key] = path.join('/api/', objectWithUrlEntries[key]);
});
};
module.exports.proxyExpressCalls = proxyExpressCalls;
module.exports.devServerProxyConfig = devServerProxyConfig;
module.exports.targetApi = targetApi;
Some of those things will be used by webpack itself, and some will be used inside the app (to correctly target api calls).
However when I try to import things in my app:
// #flow
import { buildUrl } from 'data/utils';
import type { Axios } from './flow.types';
import { targetApi } from './api';
console.log(targetApi());
I get errors. In terminal:
WARNING in ./src/data/redux/api/user.js 6:12-21 "export 'targetApi'
was not found in './api'
in browser:
api.js?d669:39 Uncaught TypeError: Cannot set property 'proxyExpressCalls' of undefined
at Object.eval (api.js?d669:39)
at eval (api.js:60)
at Object../src/data/redux/api/api.js (client.bundle.js:11620)
at __webpack_require__ (client.bundle.js:708)
at fn (client.bundle.js:113)
at eval (user.js:15)
at Object../src/data/redux/api/user.js (client.bundle.js:11668)
at __webpack_require__ (client.bundle.js:708)
at fn (client.bundle.js:113)
at eval (user.js:18)
So the problem is that when app is being bundled commonjs exports fail, but if I would use es6 export syntax then Node would fail.
I had a similar problem: I had a javascript class with some validation rules that I wanted to use in Node JS and also in the client code. What worked for me was converting everything to Common JS, the shared code, the node code, and the client code. But I still had some problems. Then I added "module": "commonjs" to my .babelrc of the folder that imports the shared code and it finally worked. This is my .babelrc file:
{
"presets": [
"react",
[
"env",
{
"debug": true,
"modules": "commonjs",
"targets": {
"browsers": [
"last 2 versions",
"safari >= 7"
],
}
}
],
],
"plugins": [
"transform-object-rest-spread",
"transform-es2015-arrow-functions",
"transform-class-properties"
]
}
Another possible solutions is (not tested!) to create a library out of your shared code, using webpack. Check the output.library and output.libraryTarget options to see which options you have to expose the library in different module systems. Then import your shared library in your node and client code.
The browser error holds the key: it looks like module.exports is null. And sure enough, you're setting values on it but it was not initialized. If instead you do this:
module.exports = {
proxyExpressCalls: proxyExpressCalls,
devServerProxyConfig: devServerProxyConfig,
targetApi: targetApi
};
(or simply set module.exports = {} first) this should solve the problem. The console warning is likely a side effect of code that keeps going even after the failure to set values on a null variable.

Resources