React-コンポーネントのユニットテストの方法
ユニットテストとは
一般的なテストは、ユニットテスト、統合テスト、機能テストのいくつかのタイプに分けられます.統合テストと機能テストは言うまでもありません.ユニットテストは、モジュール、関数、またはクラスに対して正確性検査を行うためのテスト作業であり、ここでのユニットはプログラム作業の最小作業単位である.ユニットテストは入力のみに依存し、余分な環境に依存しないでください.ユニットテストが多くの環境に依存している場合は、統合テストが必要かもしれません.
ユニットテストは、開発モデルに基づいて、次の2つに分類できます.
現在私が接触したプロジェクトはすべてBDDで、国内の先端プロジェクトはユニットテストに対する重視度がそんなに高くなくて、TDDのこのような先にユニットテストを編纂するモードの応用は多くありません.
しかし、私は本当に言いたいのは、高カバー率のユニットテストは、オンラインbug率が大幅に低下するたびに、コード再構築の基礎でもあることを保証することができます.多くの古いプロジェクトでは、開発者が退職し、新しく引き継いだ人は再構築することができず、徐々にチームの負担と呼ばれ、ラインオフできないのは、ユニットテストがないため、変更しても予測できないバグが発生するのを恐れているからだ.
ユニットテストの作成原則、推奨参考https://github.com/mawrkus/js-unit-testing-guide
Reactユニットテストフレームワーク
ここでは2つの状況に分けられます.
JestはFacebookが発表したオープンソースのJasmineフレームワークに基づくJavaScriptユニットテストツールです.内蔵されたテスト環境DOM APIサポート、ブレークスルーライブラリ、Mockライブラリなど、Spapshot Testing、Instant Feedbackなどの特性も含まれています.
EnzymeはAirbnbオープンソースのReactテストクラスライブラリで、簡潔で強力なAPIを提供し、jQueryスタイルの方法でDOM処理を行い、開発体験が非常に友好的です.オープンソースコミュニティだけでなく、React公式の推薦も受けた.
Karmaは、テストファイルを検索し、コンパイルし、断言を実行するためのテスタ、Angularチームの作品です.
Jasmineは「私たちが期待しているものを手に入れたのか」という断言庫です.describe、expect、itのような関数を提供し、関数またはメソッドがトリガーされたリスナーがあるかどうかをリスニングします.
Jest + Enzyme
Jest
Jestには、実際にはブレークスルーライブラリとランチャーが含まれています.断言ライブラリは書き込みユニットのテスト時に使用するインタフェースであり、Jestに内蔵されている断言ライブラリはJasmineであり、使用文法は以下の通りである.
describe("A suite is just a function", function() {
var a;
it("and so is a spec", function() {
a = true;
expect(a).toBe(true);
});
});
describeメソッドは、ユニットテスト(Suites)のセットを行い、it内部はテストのセットのいずれかのテスト(Specs)であることを示す.具体的な文法はhttps://jasmine.github.io/tutorials/your_first_suiteおよびhttps://jestjs.io/docs/en/api .
Jestのrunnerは簡単に使用できます.configファイルを構成すればいいです.
module.exports = {
transform: {
"^.+\\.tsx?$": "ts-jest"
},
testURL: "http://localhost",
testRegex: "\\.(test|spec)\\.(jsx?|tsx?)$",
testPathIgnorePatterns: [
"/node_modules/"
],
moduleFileExtensions: [
"ts",
"tsx",
"js",
"jsx"
],
testResultsProcessor: "jest-junit",
moduleNameMapper: {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/__mocks__/file-mock.js" ,
"\\.(css|scss)$": "/__mocks__/style-mock.js"
},
collectCoverage: true,
collectCoverageFrom: [
"**/*.{ts,tsx}",
"!**/*.d.ts",
"!**/node_modules/**"
],
coverageDirectory: "/tests-coverage" ,
coverageReporters: ["json", "html"]
}
その他の設定可能なjest configは、「https://jestjs.io/docs/en/configuration .
Enzyme
Enzymeの基本的な使い方:
import {mount, configure} from 'enzyme';
import * as Adapter from 'enzyme-adapter-react-16';
import Widget from './Widget ';
configure({ adapter: new Adapter() });
describe('try to use Enzyme', () => {
let wrapper;
let config = {};
beforeAll(() => {
wrapper = mount(<Widget />);
});
it('should have rendered widget', () => {
expect(wrapper.hasClass('container')).toEqual(true);
});
});
Enzymeには他のインタフェースも用意されていますので、参照してください.http://airbnb.io/enzyme/docs/api/.
Karma + Jasmine + Enzyme
JasmineもEnzymeもすでに紹介されていますが、Karmaについてお話しします.Karmaは運転ユニットテストの運転器(runner)である.Webpackを実行することができ、大きなスペースを提供します.Karmaを実行するのもプロファイルです.ここでは例を挙げます.
'use strict';
const path = require('path');
const fs = require('fs');
const argv = require('yargs').argv;
const KARMA_HOST_API_PATH = '/base/api';
const LOCAL_API_PATH = './api';
const api = argv.apiurl || process.env.JSAPI_URL || KARMA_HOST_API_PATH;
const isAPIBuilt = getAPIBuildStatus(api);
const files = getTestFiles(api);
const proxies = getProxies(api);
module.exports = (config) => {
config.set({
frameworks: [
'jasmine',
],
client: {
// used in ./tests/test-main.js to load API from api
args: [api, isAPIBuilt]
},
plugins: [
'karma-jasmine',
'karma-webpack',
'karma-coverage',
'karma-spec-reporter',
'karma-chrome-launcher',
'karma-phantomjs-launcher'
],
files: files,
preprocessors: {
'**/*.+(ts|tsx)': ['webpack',
// 'coverage'
],
},
// optionally, configure the reporter
coverageReporter: {
type : 'html',
dir : 'coverage/'
},
webpack: {
// karma watches the test entry points
// (you don't need to specify the entry option)
// webpack watches dependencies
devtool: 'source-map',
mode: 'development',
output: {
libraryTarget: "amd"
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx"],
alias: {
'builder': path.resolve(__dirname, 'builder/')
},
},
module: {
rules: [{
test: /\.tsx?$/,
exclude: /node_modules/,
use: [
{
loader: 'ts-loader',
options: {
transpileOnly: true,
configFile: require.resolve('./tsconfig.json')
}
}
]
}, {
test: /\.(scss|css)$/,
use: [{
loader: 'style-loader'
}, {
loader: 'css-loader',
options: {
sourceMap: process.env.NODE_ENV === 'production'? false: true
}
}, {
loader: 'postcss-loader',
options: {
plugins: []
}
}, {
loader: 'sass-loader',
options: {
sourceMap: process.env.NODE_ENV === 'production'? false: true
}
}]
}, {
test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
fallback: path.join(__dirname, './webpack/webpack-file-loader/main.js'),
outputPath: (rPath, fullPath) => {
return path.relative(__dirname, fullPath).replace(/\\/g, '/');
},
useRelativePath: true,
name: fullPath => {
return '../' + path.relative(__dirname, fullPath).replace(/\\/g, '/');
}
}
}
}]
},
stats: {
colors: true,
reasons: true
},
externals: [
function(context, request, callback) {
if (...) {
return callback(null, "commonjs " + request);
}
callback();
}
]
},
proxies: proxies,
proxyValidateSSL: false,
reporters: ['spec',
// 'coverage'
],
mime: {
'text/x-typescript': ['ts','tsx']
},
colors: true,
autoWatch: false,
browsers: ['Chrome'], // Alternatively: 'PhantomJS', 'Chrome', 'ChromeHeadless'
browserNoActivityTimeout: 100000,
customLaunchers: {
chrome_without_security: {
base: 'Chrome',
flags: ['--disable-web-security'],
displayName: 'Chrome w/o security'
}
},
// Continuous Integration mode
// if true, it capture browsers, run tests and exit
singleRun: !!argv.singleRun
});
};
function getAPIBuildStatus(api){
if(isUsingKarmaHostApi(api)){
return fs.existsSync(path.join(LOCAL_API_PATH, 'init.js'));
}else{
return !/\/src(\/)?$/.test(api);
}
}
function getTestFiles(api){
let tests = [
'./tests/test-main.js',
{pattern: path.join(argv.root, '/**/*.apitest.+(js|ts|jsx|tsx)'), included: false}
]
if(isUsingKarmaHostApi(api)){
tests = tests.concat([
{pattern: './api/**/*.+(js|html|xml|glsl|gif)', included: false, watched: false}
]);
}
return tests;
}
function getProxies(api){
if(isUsingKarmaHostApi(api)){
return {};
}else{
return {
'/base/api/': {
'target': api,
'changeOrigin': false
}
}
}
}
function isUsingKarmaHostApi(api){
return api.indexOf(KARMA_HOST_API_PATH) > -1;
}
この構成により、ユーザは、コマンドラインパラメータ
--apiurl
を介して、lodashのcdn urlのような現在のユニットテスト依存api urlを構成することができる.このパラメータが指定されていない場合、ルートディレクトリの下に./api/
があり、このディレクトリの下にあるすべてのファイルがhostされます({pattern: './api/**/*.+(js|html|xml|glsl|gif)', included: false, watched: false}
を介して).urlの提供とurlの提供がないため、リクエストは相対パスが同じですが、urlドメインが異なるため、urlを使用する場合はエージェントを有効にする必要があります(proxies: proxies
).ユニットテストを実行する前に、いくつかの構成をロードする必要があります.ここでは
./tests/test-main.js
で実行し、scriptラベルで事前にロードします.test-main.jsでは、node端に注入される変数はwindow.__karma__.config.args
によって得られ、注入変数はargs: [api, isAPIBuilt]
によって得られる.その他の構成は、https://karma-runner.github.io/2.0/config/configuration-file.html .