React组件进行单元测试的方法:
React组件可以通过单元测试框架来进行单元测试。
这里分成两种情况:
1、你的代码可以全部在node环境运行,不需要浏览器环境,那么选用Jest + Enzyme 。
Jest 是 Facebook 发布的一个开源的、基于 Jasmine 框架的 JavaScript 单元测试工具。提供了包括内置的测试环境 DOM API 支持、断言库、Mock 库等,还包含了 Spapshot Testing、 Instant Feedback 等特性。
Enzyme是Airbnb开源的 React 测试类库,提供了一套简洁强大的 API,并通过 jQuery 风格的方式进行DOM 处理,开发体验十分友好。不仅在开源社区有超高人气,同时也获得了React 官方的推荐。
2、你的代码依赖浏览器环境,那么建议选用Karma + Jasmine + Enzyme。
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)。
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)$": "<rootDir>/__mocks__/file-mock.js", "\\.(css|scss)$": "<rootDir>/__mocks__/style-mock.js" }, collectCoverage: true, collectCoverageFrom: [ "**/*.{ts,tsx}", "!**/*.d.ts", "!**/node_modules/**" ], coverageDirectory: "<rootDir>/tests-coverage", coverageReporters: ["json", "html"] }
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); }); });
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 配置一个当前单元测试依赖的api url,比如 lodash的cdn 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中,可以通过window.__karma__.config.args获得在node端注入的变量,注入变量通过args: [api, isAPIBuilt]。
更多React相关技术文章,请访问 React答疑 栏目进行学习!
以上就是如何进行React组件的单元测试?的详细内容,更多请关注易知道|edz.cc其它相关文章!