How to unit test React JSX ES6 code with KARMA? - javascript

I've written my React app with ES6. Now I would like to write my tests also with ES6. So the challenge here is to configure karma.
Together with google I came this far with karma.config.js (I've omitted parts of the config file which are the same!):
...
files: [
'../node_modules/karma-babel-preprocessor/node_modules/babel-core/browser-polyfill.js',
'../app/**/*.jsx',
'../test/**/*.jsx'],
preprocessors: {
'app/**/*.jsx': ['react-jsx', 'babel'],
'test/**/*.jsx': ['react-jsx', 'babel']
},
'babelPreprocessor': {
options: {
sourceMap: 'inline'
},
filename: function(file) {
return file.originalPath.replace(/\.jsx$/, '.es5.js');
},
sourceFileName: function(file) {
return file.originalPath;
}
},
....
What I think this setup should do: 1) compile the JSX to JS and next babel should transform ES6 to ES5. This together with the polyfill I expected it should run in phantomjs for example. But no, here is the output from karma when I run it:
PhantomJS 1.9.8 (Mac OS X) ERROR
SyntaxError: Parse error
at Projects/ES6/app/js/app.jsx:35
PhantomJS 1.9.8 (Mac OS X): Executed 0 of 0 ERROR (0.027 secs / 0 secs)
[20:36:59] Karma has exited with 1
Line 35 of app.jsx contains the actual JSX part. So, for some reason the preprocessors seems to do not so much. Any help with the preprocessors would be appreciated ?
UPDATE: I have this almost working nog. Turns out that the preprocessors I had should be swapped like this
'../app/**/*.jsx': ['babel', 'react'],
'../test/**/*.jsx': ['babel', 'react']
Now, when I run this, I get:
Uncaught ReferenceError: require is not defined
I thought I had a polyfill for that :(

I use ES6 with Browserify and JSX. For compilation I use Babel. The following configuration works for me.
karma.conf.js
...
frameworks: ['browserify', 'jasmine'],
files: [
'Component.js', // replace with your component
'__tests__/Component-test.js'
],
preprocessors: {
'Component.js': 'browserify',
'./__tests__/Component-test.js': 'browserify'
},
browserify : {
transform : ['babelify']
},
...
__tests__/Component-test.js
var React = require('react/addons');
var TestUtils = React.addons.TestUtils;
var Component = require('../Component.js');
describe('Component', () => {
it('should work', () => {
var component = <Component />;
TestUtils.renderIntoDocument(component);
expect(component).toBeTruthy();
});
});
If you have any questions let me know.

#zemirico answer did not work for me and is slightly outdated.
Here is my own setup that you can use for karma.conf.js:
...
frameworks: ['jasmine', 'browserify'],
files: [
'src/*',
'tests/*'
],
preprocessors: {
'src/*': ['browserify'],
'tests/*': ['browserify']
},
browserify: {
debug: true,
transform: ['babelify']
}
...
It uses babelify instead of reactify, and has other dependencies. Thus, .babelrc in the project root is also needed:
{
presets: ['es2015', 'react']
}
The setup also requires the dependencies below to be included in package.json file:
"devDependencies": {
"babel-preset-react": "^6.5.0",
"babelify": "^7.2.0",
"browserify": "^13.0.0",
"jasmine-core": "^2.4.1",
"karma": "^0.13.22",
"karma-browserify": "^5.0.3",
"karma-chrome-launcher": "^0.2.3",
"karma-jasmine": "^0.3.8",
"watchify": "^3.7.0",
"babel-preset-es2015": "^6.6.0",
"react": "^15.0.1",
"react-addons-test-utils": "^15.0.1",
"react-dom": "^15.0.1"
}
Usage
Create a new React component in src/my-element.jsx:
import React from 'react';
export default class MyElement extends React.Component {
constructor(props) {
super(props);
this.state = {isActive: false};
this.onClick = this.onClick.bind(this);
}
onClick() {
this.setState({isActive: !this.state.isActive});
}
render() {
return (
<div onClick={this.onClick}>{this.state.isActive ? "I am active!" : "I am not active :("}</div>
);
}
}
Then, test it as such by creating spec in tests/my-element-spec.js:
import React from 'react';
import ReactDOM from 'react-dom';
import TestUtils from 'react-addons-test-utils';
import MyElement from '../src/my-element.jsx';
describe('MyElement', () => {
// Render a checkbox with label in the document
const element = TestUtils.renderIntoDocument(<MyElement />);
const elementNode = ReactDOM.findDOMNode(element);
it('verity correct default text', () => {
expect(elementNode.textContent).toEqual('I am not active :(');
});
it ('verify text has been changed successfuly after click', () => {
// Simulate a click and verify that it is now On
TestUtils.Simulate.click(elementNode);
// Verify text has been changed successfully
expect(elementNode.textContent).toEqual('I am active!');
});
});
Demo
Working example on GitHub.

Related

Prevent Webpack from dropping my unused code

For example, my javascript file contains function that's only called when I press HTML button. As far as I know, Webpack will treat the function as a 'dead code' because it isn't used anywhere in javascript file and then dropped the code.
So, the question is how can I disable Webpack from dropping the dead code? (or any other way would also helpful)
For more information, I tried following this thread but still can't figure out how:
How to prevent unused code from being dropped during webpack build?
What I also already did was exporting everything inside index.js and import them in main.js along with other things like jquery and mathjs which then be compiled by Webpack. The output file has jquery, mathjs, and only console.log from main.js and general.js.
Even when I set the mode in development or none, the output file doesn't work at all.
// package.json
{
"private": true,
"devDependencies": {
"css-loader": "^6.7.3",
"css-minimizer-webpack-plugin": "^4.2.2",
"html-loader": "^4.2.0",
"html-webpack-plugin": "^5.5.0",
"mini-css-extract-plugin": "^2.7.2",
"purgecss-webpack-plugin": "^5.0.0",
"terser-webpack-plugin": "^5.3.6",
"uglify-js": "^3.17.4",
"uglifyjs-folder": "^3.2.0",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1"
},
"dependencies": {
"jquery": "^3.6.3",
"mathjs": "^11.5.0"
}
}
Here's my webpack.config.js (though I don't think there's problem here):
// webpack.config.js
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = [
{
mode: 'production',
module: {
rules: [
{
test: /.html$/i,
loader: "html-loader",
},
{
test: /.s?css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
],
},
optimization: {
minimizer: [...,new CssMinimizerPlugin()]
},
plugins: [
new HtmlWebpackPlugin({
filename: 'index.php'
}),
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css'
}),
],
},
]
Here's some part of the javascript file:
// main.js
import 'jquery';
import { evaluate } from 'mathjs';
console.log(evaluate('2+3'));
import js from './general.js';
// general.js
export let result;
export function clr() {
$('.input').text('0');
$('.result').text('0').removeClass('visible');
$('.input').addClass('highlight');
$('.result').removeClass('highlight');
return result = 0;
}
...
console.log('javascript loaded');
// output.js
// (...last part of the code, just to show that the javascript files are compiled successfully)
.......ImmutableDenseMatrix:Eb,Index:Ab,Spa:$b,Unit:Gb,SymbolNode:kN,FunctionNode:zN,Help:UN,Parser:GN}),FN.createProxy(hN),console.log("javascript loaded"),console.log($N("2+3"))})()})();
(I'm sorry if I can't explain it well. If there's something that I missing please let me know)

is it possible create unit for ts class by Jest?

Is it possible test this structire ? I have this structure
client/src/content/acs-signed-content-result.ts
export class AcsSignedContentResult {
testPubKey: string;
}
client/src/content/acs-signed-content.ts
import { AcsSignedContentResult } from "./content-result";
export class AcsSignedContent {
public async parse(acsSignContent: string, dsRootCert: string): Promise<AcsSignedContentResult> {
return new AcsSignedContentResult();
}
}
And my goal for test it, like separate peace, like unit test, like just execute parse method and check result, it should be object of class AcsSignedContentResult with some prop. SO for that I installed
package.json:
"ts-jest": "^27.1.4",
"ts-loader": "^9.2.6",
"typescript": "^4.6.4",
"#babel/preset-typescript": "^7.16.7",
"#types/jest": "^27.4.1",
"jest": "^28.0.3"
and
my conf
babel.config.js
module.exports = {
presets: [
['#babel/preset-env', {targets: {node: 'current'}}],
'#babel/preset-typescript',
],
};
jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
reporters: ['default', ['./node_modules/jest-html-reporter', {
includeConsoleLog: true,
includeFailureMsg: true,
includeSuiteFailure: true,
includeObsoleteSnapshots: true
}]],
roots: [
"<rootDir>/tests"
],
testMatch: [
"**/?(*.)+(spec|test).+(ts|tsx|js)"
]
};
then I created test but I'm not sure how it should be looks correctly ?
client/tests/acs-signed-content.test.js
with
import AcsSignedContent from '../src/content/acs-signed-content';
test('test acs-signed-content result', () => {
console.log("start test acs-signed-content result");
const acsSignedContent = new AcsSignedContent();
expect(acsSignedContent.parse("acsSignContent", "dsRootCert")).toBeDefined();
});
and I try this
import { AcsSignedContent } from "../src/content/acs-signed-content";
import {AcsSignedContentResult} from "../src/content/acs-signed-content-result";
jest.mock("../src/content/acs-signed-content-result");
it("should mock class B", () => {
const functionNameMock = jest.fn();
AcsSignedContentResult.mockImplementation(() => {
return {
functionName: functionNameMock
};
});
});
and when run test faced with error
● Test suite failed to run
Jest encountered an unexpected token
///
import AcsSignedContent from '../src/content/acs-signed-content';
^^^^^^
SyntaxError: Cannot use import statement outside a module
at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1773:14)
How to correct create test for this goal ?

React (Native) setup Jest to test Context component

The React Context API allows me to place the logic for the app state in one place (and avoid redux). Right now it looks like this
// Using the Context API used in react 16.3 - https://www.youtube.com/watch?v=XLJN4JfniH4
const { Provider, Consumer: ContextConsumer } = React.createContext()
class ContextProvider extends Component {
...// lot of functions ...
render() {
return (
<Provider
value={{
...this.state,
getDose: this.getDose,
getDoseRange: this.getDoseRange,
setDose: this.setDose,
checkIN: this.checkIN,
checkOUT: this.checkOUT,
getFalsifiedDrug: this.getDefaultproductData,
updatePrescriptionDose: this.updatePrescriptionDose,
}}
>
{this.props.children}
</Provider>
)
}
}
module.exports = { ContextConsumer, ContextProvider }
Entire code can be found here.
What's the best practice to build jest tests that allows me to test the functions and don't mess up the state?
(Would like to avoid using Enzyme (developed by AirBnB) - since AirBnB officially gave up using React Native)
Example
How do I make a test that confirms that when I call setDose(2) that the productData.dose was changed from "5 mg" and now equals "2 mg. But then set the state back to "5 mg" for the other test.
BONUS INFO
I'm having some trouble getting jest to work with me (so I can try out the suggested solutions)
package.json
{
"main": "node_modules/expo/AppEntry.js",
"private": true,
"scripts": {
"test": "jest --watchAll"
},
"dependencies": {
"#expo/samples": "2.1.1",
"crypto-js": "^3.1.9-1",
"date-fns": "^1.29.0",
"expo": "^28.0.0",
"invert-color": "^1.2.3",
"lodash": "^4.17.10",
"react": "16.3.1",
"react-native": "https://github.com/expo/react-native/archive/sdk-28.0.0.tar.gz",
"react-native-qrcode": "^0.2.6",
"react-native-slider": "^0.11.0",
"react-native-switch": "^1.5.0",
"react-navigation": "2.3.1",
"whatwg-fetch": "^2.0.4"
},
"devDependencies": {
"babel-eslint": "^10.0.1",
"babel-preset-expo": "^5.0.0",
"eslint": "^5.16.0",
"eslint-config-codingitwrong": "^0.1.4",
"eslint-plugin-import": "^2.17.2",
"eslint-plugin-jest": "^22.5.1",
"eslint-plugin-jsx-a11y": "^6.2.1",
"eslint-plugin-react": "^7.13.0",
"jest-expo": "^32.0.0",
"react-native-testing-library": "^1.7.0",
"react-test-renderer": "^16.8.6"
},
"jest": {
"preset": "jest-expo"
}
}
It just throws this at me
> # test /Users/norfeldt/Desktop/React-Native/MedBlockChain
> jest --watchAll
● Validation Error:
Module react-native/jest/hasteImpl.js in the haste.hasteImplModulePath option was not found.
<rootDir> is: /Users/norfeldt/Desktop/React-Native/MedBlockChain
Configuration Documentation:
https://jestjs.io/docs/configuration.html
npm ERR! Test failed. See above for more details.
I have tried things like
rm -rf node_modules/ yarn.lock package-lock.json && npm install
You could make use of react-test-renderer which you're already using in your project specs. What you need is to call testRenderer.getInstance() => check current state => invoke some methods you need to test => check updated state:
import React from "react";
import { create } from "react-test-renderer";
import { ContextProvider } from "../Context";
describe("ContextProvider", () => {
test("it updates dose correctly", () => {
const component = create(<ContextProvider />);
const instance = component.getInstance();
expect(instance.getDose()).toBe(5);
expect(instance.state.productData.dose).toBe("5 mg");
instance.setDose(2);
expect(instance.getDose()).toBe(2);
expect(instance.state.productData.dose).toBe("2 mg");
});
test("it updates something else correctly", () => {
// ...
});
});
State for other tests won't be affected.
The only thing I needed to make this work with your repo is to npm install whatwg-fetch#2.0.4 --save as described here. Hope this helps.
UPDATE
Even though this is supposed to be another question and the obvious solution is to create a new rn project and copy you code in it, but here is what I've done to fix jest errors on your code:
1) Make versions match (as described in the comments...):
"expo": "^32.0.0",
"jest-expo": "^32.0.0",
// and maybe even
"react-native": "https://github.com/expo/react-native/archive/sdk-32.0.0.tar.gz",
2) To fix an error
api.caller is not a function
use babel.config.js as described here:
module.exports = function(api) {
api.cache(true);
return {
presets: ["babel-preset-expo"],
env: {
development: {
plugins: ["transform-react-jsx-source"]
}
}
};
};
3) Use yarn due to this
If you are looking for a replacement for Enzyme, I think you should look here: https://callstack.github.io/react-native-testing-library/
That library will allow you to use the update function provided to change the values you are testing, then change them back.
Great library for React Native - the getting started page says it all. If you are interested in some code to help you get started I can work something up as well.
I suggest that you only unit-test your logic and consider avoiding component tests altogether in this case.
You can extract your methods into a "Presenter" to process your data and only test this logic. With this approach it's easy to test the presenter which has a pure input/output logic.
The presenter, for example, can look like this:
// DosePresenter.js
const processDose = (value, productData) => {
productData.dose = value.toFixed(0) + productData.dose.replace(/[\d\.]*/g, '')
productData.productionTime = Conventions.datetimeStr();
productData.hashSalt = this.makeHashSalt();
return {productData, productDataHash: getHashOfproductData(productData)};
};
module.exports = {
processDose
};
The usage:
// Context.js
import * as DosePresenter from './DosePresenter';
const setDose = (value) => this.setState(DosePresenter.processDose(value, {...this.state}));
Now it should be easier to test the logic:
// DosePresenter.test.js
describe('DosePresenter tests', () => {
let uut;
beforeEach(() => {
uut = require('./DosePresenter');
});
it('should process a dose', () => {
const value = 10;
const productData = {};
expect(uut.processDose(value, productData)).toEqual(...);
});
};

Karma not running tests that have "import" statements in karma-webpack

I have some test files with tests I'd like to run against my app.
I am attempting to use karma, karma-webpack, karma-babel-preprocessor, karma-chrome-launcher, and jasmine in my testing. My app depends on many things, including backbone, marionette, etc. My app is built using webpack, and I am attempting to use webpack to bundle my files together for testing. (I initially wanted to see if I could skip this step, i.e. simply import a file to be tested, but it seems this is impossible.)
My test script looks like
package.json (scripts section)
"test": "./node_modules/karma/bin/karma start",
The rest of the files:
karma.conf.js
var webpackConfig = require('./config/webpack/webpack.test.conf.js');
module.exports = function(config) {
config.set({
basePath: '',
frameworks: ['jasmine'],
files: [
{ pattern: 'test/**/*.spec.js', watched: true },
{ pattern: 'test/*.spec.js', watched: true }
],
exclude: [
],
preprocessors: {
'test/**/*.spec.js': ['webpack'],
'test/*.spec.js': ['webpack']
},
webpack: webpackConfig,
webpackMiddleware: {
stats: 'errors-only'
},
reporters: ['progress'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
concurrency: Infinity
})
}
test/test.spec.js This file is seen
describe("A suite", function () {
it("contains spec with an expectation", function () {
expect(true).toBe(true);
});
});
describe("Another suite", function () {
it("contains another spec with an expectation", function () {
expect(true).toBe(false);
});
});
test/models/devicegroup.spec.js This file is not seen
import backbone from 'backbone';
describe("backbone", function () {
it("containsasdfasdfasdfasdfspec with an expectation", function ()
{
expect(true).toBe(false);
});
});
My folder structure is:
- karma.conf.js
- test/
- - test.spec.js
- - models/
- - - devicegroup.spec.js
- public/
- - js/
- - - app.js
When my files don't have import statements at the top, karma will run and pass/fail as expected. Putting an import statement at the top will cause karma to ignore the file. No errors are thrown.
How can I make karma / karma-webpack run my tests that have import statements / what is the karma-safe way to import modules into my tests?
When test/models/devicegroup.spec.js does not have an import statement:
// import backbone from 'backbone';
describe("backbone", function () {
it("contains with an expectation", function () {
expect(true).toBe(false);
});
});
the terminal output is: (notice one less test is run)
When test/models/devicegroup.spec.js does have an import statement:
import backbone from 'backbone';
describe("backbone", function () {
it("contains with an expectation", function () {
expect(true).toBe(false);
});
});
the terminal output is:
I see no errors in the browser Karma opens.
EDIT:
I have experimented by adding my source files to the files and preprocessors attributes in my karma.conf.js file, as per this repo example. There was no change in behavior other than a massively increased testing time.
karma.conf.js
files: [
{ pattern: 'public/js/**/*.js', watched: true},
{ pattern: 'test/**/*.spec.js', watched: true },
// each file acts as entry point for the webpack configuration
],
preprocessors: {
// add webpack as preprocessor
'public/js/**/*.js': ['webpack'],
'test/**/*.spec.js': ['webpack'],
},
EDIT2:
For the sake of experimentation (and based off this person's struggles), I have tried the above karma.conf.js in every possible combination - only test files in files and preprocessors, only source files, test files in one but not the other, source files in one but not the other, none, both. No good results, though occasionally new errors.
Little late, but I ran into the same problem, and was searching for hours, why my imports prevent the test suite from being executed. karma-webpack-4.0.0-rc.2 brought the enlightenment by providing error messages!!
I my case a couple of modules where not found, angular-mock, jquery, angular and more.
How to fix
Put there modules into the files array in your karma.config like:
files = [
"node_modules/jquery/dist/jquery.js",
"node_modules/angular/angular.js",
"node_modules/angular-mocks/angular-mocks.js",
{ pattern: "test/**/*.ts", watched: false }
I hope, this helps someone.
EDIT
My current versions of the testing related packages:
"#types/jasmine": "^2.8.8",
"jasmine": "^3.2.0",
"jasmine-core": "^3.2.1",
"jasmine-reporters": "2.3.2",
"jasmine-ts": "^0.2.1",
"karma": "3.0.0",
"karma-chrome-launcher": "2.2.0",
"karma-jasmine": "1.1.2",
"karma-junit-reporter": "1.2.0",
"karma-phantomjs-launcher": "1.0.4",
"karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "0.0.32",
"karma-webpack": "^4.0.0-rc.2",
"typescript": "3.0.3",
"webpack": "4.17.2",
"webpack-cli": "^3.1.0",
"webpack-dev-server": "3.1.8"

Setting up Karma with Browserify to test React (ES6) components

I'm having trouble setting up a test config with Karma + Browserify for some React components. Mentioning code is written in ES6 and I've upgraded to latest Babel version (6+), which I assume is the root of all evil in this config.
Since Babel is now split and has this plugin-based approach (presets), I'm not sure how I should specify this in the karma.conf file.
My current config looks like this:
module.exports = function(config) {
config.set({
basePath: '',
browsers: ['PhantomJS'],
frameworks: ['browserify', 'jasmine'],
files: [
'app/js/**/*',
'app/__tests__/**/*'
],
preprocessors: {
'app/js/**/*': ['browserify'],
'app/__tests__/**/*': ['browserify']
},
browserify: {
debug: true,
transform: ['babelify']
},
singleRun: true
});
};
However this fails with a bundle error (Unexpected token while parsing file...). Also I get You need to include some adapter that implements __karma__.start method! error message.
It's funny that this happens for some very simple components.
Eg simple React file:
import React from 'react';
class FooterComponent extends React.Component {
render() {
return (
<footer>
This is the application's footer
</footer>
);
}
}
export default FooterComponent;
And the test file doesn't even import it. It's just an always passing test like:
describe('Testing `Footer` component.', () => {
describe('Method: none', function() {
it('Should be a passing test', function() {
expect(true).toBe(true);
});
});
});
The Babel/Browserify related packages in package.json are:
{
"babel-preset-es2015": "^6.0.15",
"babel-preset-react": "^6.0.15",
"babelify": "^7.2.0",
"browserify": "^12.0.1",
}
Any ideas are appreciated. Especially since this used to work before upgrading to Babel 6+.
Cheers!
Add a .babelrc file to your root directory:
{
presets: ['es2015', 'react']
}

Categories