Technology selection

Selection of testing tool: Jest + testing- Library

  1. jestIs an open sourcejavascriptUnit testing framework that integrates test executor, assertion library, Spy, Mock, Snapshot, and test coverage reporting.
  2. @testing-libraryIs used forDomUIComponent testing tool, which provides a series of commonly used testsAPI.

Note: the react-native testing-library has been moved to @test-library/react-native

Environment set up

Install the Jest

NPM I [email protected] NPM I [email protected] // Babel transcoding NPM I [email protected] // TS syntax parsing NPM i@types /jest // TS syntax definitionCopy the code

instructions

To use TypeScript in your project, install ts-jest, @types/jest, and add “types”: [“jest”] to the tsconfig.json file.

Note: The versions of the jest, TS-jest, and babel-jest dependency packages must be the same; otherwise, errors may be reported

Installation testing – the library

npm i @testing-library/jest-dom @testing-library/react-native @testing-library/react-hooks -D
Copy the code

instructions

  • @testing-library/jest-domProvides a set of custom JEST matchers that can be used to extend JEST. These will make your tests more declarative and easier to read and maintain.
  • @testing-library/react-nativeTest the React Native component. (Select if testing React@testing-library/react)
  • @testing-library/react-hooksCreate a simple test tool for React Hook and run it inside a function component. You can optionally download this item.

Package. json file configuration

"scripts": {..."test": "jest test --verbose -u --watch",}"jest": {
	"preset": "react-native"
}
Copy the code

Babel file configuration

  • If in the projectbebelThe configuration uses.babelrc.js, the file needs to be converted tobabel.config.js(May affect the original compilation results of the project)
  • If you convert tobabel.config.jsFiles affect project compilation results and can be retained.babelrc.jsFile, and create a new onebabel.config.jsFile and willpresetsA statement tobabel.config.js

Example:

// babel.config.js

module.exports = (api) = > {
  api.cache.never();

  return {
    "presets": process.env.MINI_PROGRAM === 'true' ? [] : ['module:metro-react-native-babel-preset']}};Copy the code

The difference between the Babel configuration file.babelrc.js and babel.config.js

Understand babel.config.js and babelrc

Jest. Config. js file configuration

Jest. Config. js is created in the root directory of the project, and the configuration can be referred to as follows (the author project uses ts syntax, js and other configurations can be added by himself).

const { defaults } = require('ts-jest/presets');

module.exports = { ... defaults,preset: 'react-native'.globals: {
    'ts-jest': {
      babelConfig: true,}},// The.tsx/.jsx file in the tests folder in the same directory as the configuration file
  testRegex: '(/tests/.*\\.(test|spec))\\.[tj]sx? $'.// Define how the file is compiled
  transform: {
    '^.+\\.tsx? $': 'ts-jest',},// defines dependency packages that ignore jEST execution
  transformIgnorePatterns: [
    'node_modules/(? ! (react-native|@testing-library|react-navigation|@react-navigation/.*|@react-native-community)/)',].testPathIgnorePatterns: ['<rootDir>/node_modules/'.'\\.snap$'].// Address of the directory generated by the cache file, ignored in.gitignore
  cacheDirectory: '.jest/cache'.testEnvironment: 'jsdom'.moduleNameMapper: {
    '^[@./a-zA-Z0-9$_-]+\\.(png|gif)$': '<rootDir>/node_modules/react-native/Libraries/Image/RelativeImageStub',}};Copy the code

See jest.config.js at the bottom of the document for more config instructions

Basic knowledge of

A simple example

A common test file is as follows:

// sum.test.ts
const sum = (a, b) = > {
	return a + b;
}

describe('Unit test explain'.() = > {
	test('sum success'.() = > {
    expect(sum(1.2)).toBe(3); })})Copy the code
  • describeTest suites are typically decomposed into components. This means that we can break down the overall functionality of a component, by module or by function. whiletest/itIs where individual tests are performed and describes each test function point.
  • testCalled a test case, it takes two parameters. The first parameter is the description of the use case, and the second is the test function, which defines the logic. withitSynonymous.
  • expectThe whole line is called an assertion. That means expectations1 + 2Whether is equal to the3.
  • toBeIt’s a matcher. It matchesexpectIs the expected value equal to the value in the matcher.

Common matchers

  • toBe(value)Is it exactly equal? Is it equal= = =
  • toBeNull(value)Whether it isnull
  • toBeUndefined()Whether it isundefined
  • toBeNaN()matchingNaN
  • toBeTruthy()matchingtrue
  • toBeFalsy()matchingfalse
  • .notThe subsequent matches are reversed
  • toMatch(regexpOrString)Checks for a string match, passable string, or regular expression
  • toMatchObject(object)Determines whether an object/array is a subset
  • toContain(item)Matches whether the array /Set/ string containsitem
  • **toContainEqual(item)Matches whether the array contains a specific object
  • toHaveProperty(keyPath, value)Matches deeply nested attributes in an object, judged in the specifiedkeyPathIf I havevalueattribute
  • toHaveLength(number)A match objectlength
  • toThrow(err)/toThrowError(err)Match the abnormal
  • expectTo match an exception
  • toBeCalled()/toHaveBeCalled()Whether the match function is executed
  • toReturn()/toHaveReturned()Whether the match function returns a value
  • toReturnWith(value)/toHaveReturnedWith(value)Whether the value returned by the matching function matches

Common matchers are listed above, and more information can be found in the expected matchers

Testing Library

Let’s look at another example:

import React from 'react';
import "setimmediate"; // Setimmediate Do not forget to introduce mediate
import { render } from '@testing-library/react-native';
import Button from '.. /lib/button';

describe('Button unit test'.() = > {
	test('render success'.() = > {
  	const { getByTestId } = render(<Button testID='button' />);
    
    // Expecting to find the element testID button
    expect(() = > getByTestId('button')).not.toThrow(/Unable to find an element with testID/); })})Copy the code

The above example is to test whether the Button component has successfully rendered and to see if the corresponding Dom element can be found by testID.

  • render()Used to render components, transparentgetByTestIdThe method is used for the followingtestIDThe search for
  • getByTestId()Look for rendered elementstestIDbtnThe elements of the
  • toThrow(err)matcherRequired functionTo match the exception, so we pass in() => getByTestId('btn') And the expectation not to throw cannot be based ontestID Find node errors

Common query

First look at the overview:

  • getBy...Returns the matching nodes of the query, and throws a descriptive error if none, or more than one, is found.
  • quertBy...Returns the matched node of the query, or if there are no matched elementsnull. If more than one match is found, an error is thrown.
  • findBy...Returns aPromisethePromiseParses when a match is found. If no element is matched, or the matching time (default: 1000ms) is exceeded,PromiseWill be rejected.

Ditto, getAllBy… quertAllBy… findAllBy… Query all matched nodes.

Common queries:

  • getByRoleQuery elements with a given role.
  • getByLabelTextThe querylabelThe element that matches the given text.
  • getByPlaceholderTextQueries all elements that match placeholder text.
  • getByTextQuery all text nodes for elements that match the given text.
  • getByTestIdThe query element containsdata-testid="${yourId}"Is provided by default in RN componentstestIDProperties.

For more information about queriers, see queriers

Test case

A snapshot of the test

A snapshot test is a snapshot file of the render results under different conditions during the first run of the test. Each subsequent run of the snapshot test will be compared with the first run.

it("test snapshot".() = > { 
  const component = render(<App />); 
  
  expect(component.toJSON()).toMatchSnapshot();
});
Copy the code

Use Render to render the App component, and then match the snippet of the App component to the snapshot.

At this point, if you modify app.js, the test will fail because the snapshot will no longer be eligible. Use NPM test — -u to update the snapshot.

Test element

test('render success'.() = > {
  const { getByText } = render(<Text>Description information</Text>)

  expect(getByText('Description')).not.toThrow(/Unable to find an element with text/);
})
Copy the code

instructions

Render a Text element, using getByText(‘ description ‘) to match the rendering component for any Text element that matches the description, if not, an error is thrown.

Test events

// Simulate the onClose event
const onClose = jest.fn();

const { getByTestId } = render(<Button onPress={onClose} />);

act(() = > {
  // Run the click event of testID for the button element
	fireEvent.press(getByTestId('button'));
})

// The onClose method is called after the button element is clicked
expect(onClose).toBeCalled();
Copy the code

instructions

In the example above, we need to care if the Button component’s click event is called correctly, and we need to use the Mock function.

  1. usejest.fn()Define a mock function, and then setonCloseThe incoming sampleButtonIn the click property of the component, the next step is to execute the click event. In a nutshell, thisfireEventTo receive aDomNodes and simulate triggeringDomEvents, such as: click, content change, slide, etc.
  2. throughonCloseThe function is toBeCalled to determine whether the match is completeonCloseMethod testing.

Testing asynchronous operations

// Simulate a data request method
const fetchData = (fn) = > {
	axios.get('https://api_url/')
  .then(response= > {
  	fn(response.data)
  })
}

// Use the done() method
// The test case does not end until the done method is executed
test('unit test'.(done) = > {
	fetchData((data) = > {
  	expect(data).toMatchObject({
    	code: 200}) done(); })})Copy the code

We cannot test asynchronous operations with regular logic, and the test case does not wait for the request to end. So the normal logic is executed before the asynchronous operation.

The data request returns a Promise object

// Data request method
const fetchData = () = > {
	return axios.get('https://api_url/')}// Use the done() method
test('unit test'.(done) = > {
	fetchData().then(res= > {
  	expect(res.data).toMatchObject({
    	code: 200}) done(); })})// Or just use the return method
test('unit test'.() = > {
	return fetchData().then(res= > {
  	expect(res.data).toMatchObject({
    	code: 200})})})Copy the code

Test the hooks

Note: As a testing principle, avoid making assertions about props or state of a component.

We use @testing-library/react-hooks to test hooks, because in principle they test Dom and UI and the effects of interaction events on Dom\UI. So we can’t actually get props or state defined inside the component.

// ...
import { renderHook } from '@testing-library/react-hooks';

// Build custom hooks that change data
const useVisibleAction = () = > {
	const [visible, setVisible] = useState(false);
  const update = useCallback((value: boolean) = > setVisible(value), []);
  
  return {
  	update,
    value: visible,
  }
}

it('unit test'.() = > {
	const { result } = renderHook(useVisibleAction);
  const { getByTestId } = render(<Button testID='testID' onPress={value= > result.current.update(value)} />);
  
  act(() = > {
    fireEvent.press(getByTestId('testID'));
  })

  expect(result.current.value).toBeTruthy();
})
Copy the code

By building a custom hook function, such as useVisibleAction in the code, we define update to perform setVisible’s method and eventually return {update, value: Visible}.

In the Button component’s onPress method, define the update execution, followed by calling the onPress method via FireEvent. press.

Finally, we can simulate the execution of hook, and obtain the value of visible after execution through result.current. Value to make assertions and complete the test.

Be careful when using

The test file

Install jEST dependencies globally

npm install -g jest-cli
Copy the code

Testing a single file

To test individual files, simply run the jest demo.test.tsx command

TSX matches all test files that end in demo.test.tsx.

For example, if you run the jest demo.test. TSX command, test-demo.test. TSX and testdemo.test.

Test all files

Execute the script to test and listen to all test files through the NPM run test command

The mock simulation library

Mock Mocks the tag in React-native

// TouchableHighlight
jest.mock('react-native/Libraries/Components/Touchable/TouchableHighlight'.() = > 'TouchableHighlight');

// Text
jest.mock('react-native/Libraries/Text/Text'.() = > 'Text');
Copy the code

Mock @react-native-clipboard/clipboard library methods: setString(), getString()

jest.mock('@react-native-clipboard/clipboard'.() = > {
  let string = undefined;

  return {
    setString: (text) = > string = text,
    getString: () = > string}; });Copy the code

jest.config.js

Js For more configuration options see Configuring Jest

module.exports = {
  // Jest configuration base presets
  preset: 'react-native'.// An array of file extensions used by the module
  moduleFileExtensions: ['ts'.'tsx'.'js'.'jsx'.'json'.'node'].// Test the mode of the test file (TSX or JSX files in the Tests directory)
  testRegex: '(/tests/.*\\.(test|spec))\\.[tj]sx? $'.// Mapping of paths from regular expressions to converters, which are modules that provide synchronization to convert source files
  transform: {
    '^.+\\.tsx? $': 'ts-jest'.// Convert ts module files
    '\\.[jt]sx? $': 'babel-jest'.// Convert js module files
  },
  
  // Allows transmission of untranslated modules that Jest will not understand from third party modules
  transformIgnorePatterns: [
    'node_modules/(? ! (@package-name)/)',].// Files in matching paths will skip coverage information
  testPathIgnorePatterns: ['<rootDir>/node_modules/'.'\\.snap$'].// The directory that stores its cached dependency information
  cacheDirectory: '.jest/cache'.// Set up jsDOM to use a browser-like environment. The default environment is node.js
  testEnvironment: 'jsdom'.// Mapping from regular expressions to module names or arrays of module names
   moduleNameMapper: {
    '^[@./a-zA-Z0-9$_-]+\\.(png|gif)$': '<rootDir>/node_modules/react-native/Libraries/Image/RelativeImageStub',},// Whether all modules in the test are automatically simulated
  automock: false.// Jest runs all tests and stops Jest from running tests after n failures
  bail: 1.// Bail: true
  
  // Whether mock calls and instances are cleared before each test
  clearMocks: false.// Whether coverage information should be collected during test execution can significantly slow down testing
  collectCoverage: false.// Collect the matching file for coverage information. Items at the end of the array are overlaid to cover previous matches, valid when collectCoverage is true
  collectCoverageFrom: [
    "**/*.{js,jsx}".! "" **/node_modules/**".! "" **/vendor/**"].// Output directory for coverage files
  coverageDirectory: 'coverage'.// Which provider to use to insert code for coverage, default: 'label', 'V8' is currently experimental
  coverageProvider: 'label'.// Summary of coverage report information output by the console
  moduleFileExtensions: ['json'.'lcov'.'text'.'clover'].// Whether each individual test should be reported at run time, individual tests default to true
  verbose: false,}...Copy the code

How to use React Testing Library and Jest to test React applications