Skip to content

Instantly share code, notes, and snippets.

@mateuszsokola
Last active July 30, 2017 18:14
Show Gist options
  • Save mateuszsokola/bf54c04f54aa00a34597b3016e46992a to your computer and use it in GitHub Desktop.
Save mateuszsokola/bf54c04f54aa00a34597b3016e46992a to your computer and use it in GitHub Desktop.
Testing with Jest

Testing with Jest

Jest framework allows us to set up testing our application without creating complicated configurations. It includes the most of necessary tools needed to have good testing suite such as:

  • mocking - i love that feature
  • jasmine's assertion library
  • code coverage (with reports)
  • easy to plug-in into CI systems
  • based on JSDOM, allows us to test DOM updates without running headless browser.
  • created for React

Mocking

Mocking with Jest is super easy, we can mock dependencies without struggle. We don't need to use Dependency Injection to make mocking possible, even when we need to mock window object.

import React from 'react'
import { shallow } from 'enzyme'
import CityPair from '../../src/components/CityPair'
import CityTextInput from '../../src/components/CityTextInput'
const testData = {
cityA: {
id: 1,
label: 'Amburgo'
},
cityB: {
id: 2,
label: 'Wedding'
}
}
describe('<CityPair />', () => {
it('renders one <CityTextInput /> component', () => {
const wrapper = shallow(
<CityPair cityA={testData.cityA} cityB={testData.cityB} updateCityPair={jest.fn}/>
)
const elements = wrapper.find(CityTextInput)
expect(elements.length).toBe(1)
});
it('renders the name of cityA in <span/> tag', () => {
const wrapper = shallow(
<CityPair cityA={testData.cityA} cityB={testData.cityB} updateCityPair={jest.fn}/>
)
const elements = wrapper.find('span')
expect(elements.text()).toBe('Amburgo')
});
})
// file: src/feature-toggle-native.js
import fs from 'fs';
const CONFIG_FEATURE_TOGGLE_PROPERTY = 'feature_toggle';
const CONFIG_FILE_ENCODING = 'utf8';
const CONFIG_FILE_PATH = './config/';
/**
* Loads the config file.
* NOTE: Please keep it private!
*
* @private
* @param env
* @returns {Object}
*/
function loadConfigFile (env) {
try {
const configFileName = (env === 'development') ? 'development.json' : 'production.json';
const filePath = `${CONFIG_FILE_PATH}${configFileName}`;
const fileContent = fs.readFileSync(filePath, CONFIG_FILE_ENCODING);
return JSON.parse(fileContent);
} catch (e) {
console.log('[FeatureToggle] Couldn\'t read the config file');
}
return {};
}
/**
* Reads the config file and retrieves the array of enabled features.
*
* @returns {[...string]}
*/
export default function retrieveEnabledFeatures () {
const configFile = loadConfigFile(process.env.NODE_ENV);
const features = configFile[CONFIG_FEATURE_TOGGLE_PROPERTY];
// returns only enabled features
return Object.keys(features).filter(feature => (features[feature] === true));
}
// file: src/__tests__/feature-toggle-native.test.js
import fs from 'fs';
import retrieveEnabledFeatures from '../feature-toggle-native';
jest.mock('fs');
describe('#retrieveEnabledFeatures', () => {
const NODE_ENV = process.env.NODE_ENV;
const DEV_DEFAULT = {
feature_toggle: {
foobar: true,
foo: true,
bar: true,
},
};
const PROD_DEFAULT = {
feature_toggle: {
foo: true,
bar: true,
},
};
beforeEach(() => {
// set mock - json output
fs.setCustomOutput({
'./config/development.json': JSON.stringify(DEV_DEFAULT),
'./config/production.json': JSON.stringify(PROD_DEFAULT),
});
});
afterEach(() => {
process.env.NODE_ENV = NODE_ENV;
});
it('retrieves the array of features enabled on the developement environment', () => {
process.env.NODE_ENV = 'development';
expect(retrieveEnabledFeatures()).toEqual([
'foobar',
'foo',
'bar',
]);
});
it('retrieves the array of features enabled on the production environment when could not recognize the environment', () => {
process.env.NODE_ENV = 'any-unknown-value-returns-production';
expect(retrieveEnabledFeatures()).toEqual([
'foo',
'bar',
]);
});
it('returns the empty array of features enabled when config does not contain the feature_toggle property', () => {
process.env.NODE_ENV = 'development';
fs.setCustomOutput(Object.create(null));
expect(retrieveEnabledFeatures()).toEqual([]);
});
});
// file: __mocks__/fs.js
/**
* Find more information about mocking in link below.
* @link https://facebook.github.io/jest/docs/manual-mocks.html
*/
const fs = jest.genMockFromModule('fs');
// it doesn't inherit anything from Object
let mockFiles = Object.create(null);
/**
* Assigns files and their content to completely empty object.
* @param files
*/
function setCustomOutput (files) {
mockFiles = Object.create(null);
Object
.keys(files)
.forEach((fileName) => {
mockFiles[fileName] = files[fileName];
});
}
// mock readFileSync method
function readFileSync (filePath) {
return mockFiles[filePath];
}
// override methods
fs.setCustomOutput = setCustomOutput;
fs.readFileSync = readFileSync;
module.exports = fs;
// file: src/window-example.js
export function getLogoutPath () {
const locale = window.location.pathname.split('/')[1];
return locale ? `/${locale}/logout` : '/logout';
}
export function getLoginPath () {
return '/login';
}
// file: src/__tests__/window-example.test.js
import {getLogoutPath} from '../window-example';
describe('#getLogoutPath', () => {
it('returns logout path with austrian country code', () => {
Object.defineProperty(window.location, 'pathname', {
writable: true,
value: '/at/12412-xing'
});
expect(getLogoutPath()).toBe('/at/logout');
});
it('returns logout path without country code', () => {
Object.defineProperty(window.location, 'pathname', {
writable: true,
value: ''
});
expect(getLogoutPath()).toBe('/logout');
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment