From 384dfe2ee2b2cdd67839afa1b5a5ccc8e833959d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 3 Jan 2024 21:31:40 +0000 Subject: [PATCH] deploy: 21254b059a549b324f96a8277e30d1d01e278b91 --- 404.html | 4 ++-- assets/js/{01df2f6c.fce52876.js => 01df2f6c.dd30f979.js} | 2 +- assets/js/{14f61f32.29a68f7a.js => 14f61f32.f5d2aaf6.js} | 2 +- assets/js/1bdd165f.66a4d1db.js | 1 + assets/js/1bdd165f.7d328ada.js | 1 - assets/js/{7ad239b9.93a79b77.js => 7ad239b9.95f29375.js} | 2 +- assets/js/{add06ab1.933ff52f.js => add06ab1.76cb6be1.js} | 2 +- assets/js/{c8229a80.1784de8d.js => c8229a80.48837734.js} | 2 +- ...{runtime~main.4e03a67a.js => runtime~main.77ca6133.js} | 2 +- docs/api-queries.html | 8 ++++---- docs/api.html | 6 +++--- docs/eslint-plugin-testing-library.html | 4 ++-- docs/faq.html | 4 ++-- docs/getting-started.html | 4 ++-- docs/how-should-i-query.html | 4 ++-- docs/jest-matchers.html | 4 ++-- docs/migration-jest-native.html | 4 ++-- docs/migration-v11.html | 6 +++--- docs/migration-v12.html | 6 +++--- docs/migration-v2.html | 4 ++-- docs/migration-v7.html | 4 ++-- docs/migration-v9.html | 4 ++-- docs/react-navigation.html | 6 +++--- docs/redux-integration.html | 4 ++-- docs/testing-env.html | 4 ++-- docs/troubleshooting.html | 6 +++--- docs/understanding-act.html | 4 ++-- docs/user-event.html | 4 ++-- index.html | 4 ++-- search.html | 4 ++-- 30 files changed, 58 insertions(+), 58 deletions(-) rename assets/js/{01df2f6c.fce52876.js => 01df2f6c.dd30f979.js} (75%) rename assets/js/{14f61f32.29a68f7a.js => 14f61f32.f5d2aaf6.js} (84%) create mode 100644 assets/js/1bdd165f.66a4d1db.js delete mode 100644 assets/js/1bdd165f.7d328ada.js rename assets/js/{7ad239b9.93a79b77.js => 7ad239b9.95f29375.js} (79%) rename assets/js/{add06ab1.933ff52f.js => add06ab1.76cb6be1.js} (89%) rename assets/js/{c8229a80.1784de8d.js => c8229a80.48837734.js} (65%) rename assets/js/{runtime~main.4e03a67a.js => runtime~main.77ca6133.js} (79%) diff --git a/404.html b/404.html index 434f4084..1c796b33 100644 --- a/404.html +++ b/404.html @@ -4,13 +4,13 @@ Page Not Found | React Native Testing Library - +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- + \ No newline at end of file diff --git a/assets/js/01df2f6c.fce52876.js b/assets/js/01df2f6c.dd30f979.js similarity index 75% rename from assets/js/01df2f6c.fce52876.js rename to assets/js/01df2f6c.dd30f979.js index e9801e6d..8c982aac 100644 --- a/assets/js/01df2f6c.fce52876.js +++ b/assets/js/01df2f6c.dd30f979.js @@ -1 +1 @@ -"use strict";(self.webpackChunkreact_native_testing_library_website=self.webpackChunkreact_native_testing_library_website||[]).push([[278],{3905:(e,t,n)=>{n.d(t,{Zo:()=>u,kt:()=>g});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var s=a.createContext({}),c=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},u=function(e){var t=c(e.components);return a.createElement(s.Provider,{value:t},e.children)},p="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},d=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,i=e.originalType,s=e.parentName,u=l(e,["components","mdxType","originalType","parentName"]),p=c(n),d=r,g=p["".concat(s,".").concat(d)]||p[d]||m[d]||i;return n?a.createElement(g,o(o({ref:t},u),{},{components:n})):a.createElement(g,o({ref:t},u))}));function g(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=n.length,o=new Array(i);o[0]=d;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[p]="string"==typeof e?e:r,o[1]=l;for(var c=2;c{n.d(t,{Z:()=>o});var a=n(7294),r=n(3743);const i={tableOfContentsInline:"tableOfContentsInline_prmo"};function o(e){let{toc:t,minHeadingLevel:n,maxHeadingLevel:o}=e;return a.createElement("div",{className:i.tableOfContentsInline},a.createElement(r.Z,{toc:t,minHeadingLevel:n,maxHeadingLevel:o,className:"table-of-contents",linkClassName:null}))}},3743:(e,t,n)=>{n.d(t,{Z:()=>g});var a=n(7462),r=n(7294),i=n(6668);function o(e){const t=e.map((e=>({...e,parentIndex:-1,children:[]}))),n=Array(7).fill(-1);t.forEach(((e,t)=>{const a=n.slice(2,e.level);e.parentIndex=Math.max(...a),n[e.level]=t}));const a=[];return t.forEach((e=>{const{parentIndex:n,...r}=e;n>=0?t[n].children.push(r):a.push(r)})),a}function l(e){let{toc:t,minHeadingLevel:n,maxHeadingLevel:a}=e;return t.flatMap((e=>{const t=l({toc:e.children,minHeadingLevel:n,maxHeadingLevel:a});return function(e){return e.level>=n&&e.level<=a}(e)?[{...e,children:t}]:t}))}function s(e){const t=e.getBoundingClientRect();return t.top===t.bottom?s(e.parentNode):t}function c(e,t){var n;let{anchorTopOffset:a}=t;const r=e.find((e=>s(e).top>=a));if(r){var i;return function(e){return e.top>0&&e.bottom{e.current=t?0:document.querySelector(".navbar").clientHeight}),[t]),e}function p(e){const t=(0,r.useRef)(void 0),n=u();(0,r.useEffect)((()=>{if(!e)return()=>{};const{linkClassName:a,linkActiveClassName:r,minHeadingLevel:i,maxHeadingLevel:o}=e;function l(){const e=function(e){return Array.from(document.getElementsByClassName(e))}(a),l=function(e){let{minHeadingLevel:t,maxHeadingLevel:n}=e;const a=[];for(let r=t;r<=n;r+=1)a.push("h"+r+".anchor");return Array.from(document.querySelectorAll(a.join()))}({minHeadingLevel:i,maxHeadingLevel:o}),s=c(l,{anchorTopOffset:n.current}),u=e.find((e=>s&&s.id===function(e){return decodeURIComponent(e.href.substring(e.href.indexOf("#")+1))}(e)));e.forEach((e=>{!function(e,n){n?(t.current&&t.current!==e&&t.current.classList.remove(r),e.classList.add(r),t.current=e):e.classList.remove(r)}(e,e===u)}))}return document.addEventListener("scroll",l),document.addEventListener("resize",l),l(),()=>{document.removeEventListener("scroll",l),document.removeEventListener("resize",l)}}),[e,n])}function m(e){let{toc:t,className:n,linkClassName:a,isChild:i}=e;return t.length?r.createElement("ul",{className:i?void 0:n},t.map((e=>r.createElement("li",{key:e.id},r.createElement("a",{href:"#"+e.id,className:null!=a?a:void 0,dangerouslySetInnerHTML:{__html:e.value}}),r.createElement(m,{isChild:!0,toc:e.children,className:n,linkClassName:a}))))):null}const d=r.memo(m);function g(e){let{toc:t,className:n="table-of-contents table-of-contents__left-border",linkClassName:s="table-of-contents__link",linkActiveClassName:c,minHeadingLevel:u,maxHeadingLevel:m,...g}=e;const f=(0,i.L)(),h=null!=u?u:f.tableOfContents.minHeadingLevel,v=null!=m?m:f.tableOfContents.maxHeadingLevel,k=function(e){let{toc:t,minHeadingLevel:n,maxHeadingLevel:a}=e;return(0,r.useMemo)((()=>l({toc:o(t),minHeadingLevel:n,maxHeadingLevel:a})),[t,n,a])}({toc:t,minHeadingLevel:h,maxHeadingLevel:v});return p((0,r.useMemo)((()=>{if(s&&c)return{linkClassName:s,linkActiveClassName:c,minHeadingLevel:h,maxHeadingLevel:v}}),[s,c,h,v])),r.createElement(d,(0,a.Z)({toc:k,className:n,linkClassName:s},g))}},5374:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>l,default:()=>d,frontMatter:()=>o,metadata:()=>s,toc:()=>u});var a=n(7462),r=(n(7294),n(3905)),i=n(3901);const o={id:"troubleshooting",title:"Troubleshooting"},l=void 0,s={unversionedId:"troubleshooting",id:"troubleshooting",title:"Troubleshooting",description:"This guide describes common issues found by users when integrating React Native Test Library to their projects:",source:"@site/docs/Troubleshooting.md",sourceDirName:".",slug:"/troubleshooting",permalink:"/react-native-testing-library/docs/troubleshooting",draft:!1,editUrl:"https://github.com/callstack/react-native-testing-library/blob/main/website/docs/Troubleshooting.md",tags:[],version:"current",frontMatter:{id:"troubleshooting",title:"Troubleshooting"},sidebar:"docs",previous:{title:"ESLint Plugin Testing Library Compatibility",permalink:"/react-native-testing-library/docs/eslint-plugin-testing-library"},next:{title:"Testing Environment",permalink:"/react-native-testing-library/docs/testing-env"}},c={},u=[{value:"Example repository",id:"example-repository",level:2},{value:"Undefined component error",id:"undefined-component-error",level:2},{value:"Mocking React Native",id:"mocking-react-native",level:3},{value:"Act warnings",id:"act-warnings",level:2}],p={toc:u},m="wrapper";function d(e){let{components:t,...n}=e;return(0,r.kt)(m,(0,a.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"This guide describes common issues found by users when integrating React Native Test Library to their projects:"),(0,r.kt)(i.Z,{toc:u,mdxType:"TOCInline"}),"## Matching React Native, React & React Test Renderer versions",(0,r.kt)("p",null,"Check that you have matching versions of core dependencies:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"React Native"),(0,r.kt)("li",{parentName:"ul"},"React"),(0,r.kt)("li",{parentName:"ul"},"React Test Renderer")),(0,r.kt)("p",null,"React Native uses different versioning scheme from React, you can use ",(0,r.kt)("a",{parentName:"p",href:"https://react-native-community.github.io/upgrade-helper/"},"React Native Upgrade Helper")," to find the correct matching between React Native & React versions. In case you use Expo, you should use dependency versions recommended by them and set by ",(0,r.kt)("inlineCode",{parentName:"p"},"expo upgrade")," command."),(0,r.kt)("p",null,"React Test Renderer usually has same major & minor version as React, as they are closely related and React Test Renderer is part of ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/facebook/react"},"React monorepo"),"."),(0,r.kt)("p",null,"Related issues: ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/callstack/react-native-testing-library/issues/1061"},"#1061"),", ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/callstack/react-native-testing-library/issues/938"},"#938"),", ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/callstack/react-native-testing-library/issues/920"},"#920")),(0,r.kt)("p",null,"Errors that might indicate that you are facing this issue:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"TypeError: Cannot read property 'current' of undefined")," when calling ",(0,r.kt)("inlineCode",{parentName:"li"},"render()")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"TypeError: Cannot read property 'isBatchingLegacy' of undefined")," when calling ",(0,r.kt)("inlineCode",{parentName:"li"},"render()"))),(0,r.kt)("h2",{id:"example-repository"},"Example repository"),(0,r.kt)("p",null,"We maintain an ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/callstack/react-native-testing-library/tree/main/examples/basic"},"example repository")," that showcases a modern React Native Testing Library setup with TypeScript, etc."),(0,r.kt)("p",null,"In case something does not work in your setup you can refer to this repository for recommended configuration."),(0,r.kt)("h2",{id:"undefined-component-error"},"Undefined component error"),(0,r.kt)("blockquote",null,(0,r.kt)("p",{parentName:"blockquote"},"Warning: React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined.")),(0,r.kt)("p",null,"This frequently happens when you mock a complex module incorrectly, e.g.:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"jest.mock('@react-navigation/native', () => {\n return {\n useNavigation: jest.fn(),\n };\n})\n")),(0,r.kt)("p",null,"The above mock will mock ",(0,r.kt)("inlineCode",{parentName:"p"},"useNavigation")," hook as intended, but at the same time all other exports from ",(0,r.kt)("inlineCode",{parentName:"p"},"@react-navigation/native")," package are now ",(0,r.kt)("inlineCode",{parentName:"p"},"undefined"),". If you want to use ",(0,r.kt)("inlineCode",{parentName:"p"},"NavigationContainer")," component from the same package it will be ",(0,r.kt)("inlineCode",{parentName:"p"},"undefined")," and result in the error above."),(0,r.kt)("p",null,"In order to mock only a part of given package you should re-export all other exports using ",(0,r.kt)("inlineCode",{parentName:"p"},"jest.requireActual")," helper:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"jest.mock('@react-navigation/native', () => {\n return {\n ...jest.requireActual('@react-navigation/native'),\n useNavigation: jest.fn(),\n };\n})\n")),(0,r.kt)("p",null,"That way the mock will re-export all of the ",(0,r.kt)("inlineCode",{parentName:"p"},"@react-navigation/native")," members and overwrite only the ",(0,r.kt)("inlineCode",{parentName:"p"},"useNavigation")," hook."),(0,r.kt)("p",null,"Alternatively, you can use ",(0,r.kt)("inlineCode",{parentName:"p"},"jest.spyOn")," to mock package exports selectively."),(0,r.kt)("h3",{id:"mocking-react-native"},"Mocking React Native"),(0,r.kt)("p",null,"In case of mocking ",(0,r.kt)("inlineCode",{parentName:"p"},"react-native")," package you should not mock the whole package at once, as this approach has issues with ",(0,r.kt)("inlineCode",{parentName:"p"},"jest.requireActual")," call. In this case it is recommended to mock particular library paths inside the package, e.g.:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter');\n")),(0,r.kt)("h2",{id:"act-warnings"},"Act warnings"),(0,r.kt)("p",null,"When writing tests you may encounter warnings connected with ",(0,r.kt)("inlineCode",{parentName:"p"},"act()")," function. There are two kinds of these warnings:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"sync ",(0,r.kt)("inlineCode",{parentName:"li"},"act()")," warning - ",(0,r.kt)("inlineCode",{parentName:"li"},"Warning: An update to Component inside a test was not wrapped in act(...)")),(0,r.kt)("li",{parentName:"ul"},"async ",(0,r.kt)("inlineCode",{parentName:"li"},"act()")," warning - ",(0,r.kt)("inlineCode",{parentName:"li"},"Warning: You called act(async () => ...) without await"))),(0,r.kt)("p",null,"You can read more about ",(0,r.kt)("inlineCode",{parentName:"p"},"act()")," function in our ",(0,r.kt)("a",{parentName:"p",href:"https://callstack.github.io/react-native-testing-library/docs/understanding-act"},"understanding ",(0,r.kt)("inlineCode",{parentName:"a"},"act")," function guide"),"."),(0,r.kt)("p",null,"Normally, you should not encounter sync ",(0,r.kt)("inlineCode",{parentName:"p"},"act()")," warnings, but if that happens this probably indicate an issue with your test and should be investigated."),(0,r.kt)("p",null,"In case of async ",(0,r.kt)("inlineCode",{parentName:"p"},"act()")," function this might happen more or less randomly, especially if your components contain async logic. So far this warning does not seem to affect test correctness."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkreact_native_testing_library_website=self.webpackChunkreact_native_testing_library_website||[]).push([[278],{3905:(e,t,n)=>{n.d(t,{Zo:()=>u,kt:()=>g});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var s=a.createContext({}),c=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},u=function(e){var t=c(e.components);return a.createElement(s.Provider,{value:t},e.children)},p="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},d=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,i=e.originalType,s=e.parentName,u=l(e,["components","mdxType","originalType","parentName"]),p=c(n),d=r,g=p["".concat(s,".").concat(d)]||p[d]||m[d]||i;return n?a.createElement(g,o(o({ref:t},u),{},{components:n})):a.createElement(g,o({ref:t},u))}));function g(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=n.length,o=new Array(i);o[0]=d;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[p]="string"==typeof e?e:r,o[1]=l;for(var c=2;c{n.d(t,{Z:()=>o});var a=n(7294),r=n(3743);const i={tableOfContentsInline:"tableOfContentsInline_prmo"};function o(e){let{toc:t,minHeadingLevel:n,maxHeadingLevel:o}=e;return a.createElement("div",{className:i.tableOfContentsInline},a.createElement(r.Z,{toc:t,minHeadingLevel:n,maxHeadingLevel:o,className:"table-of-contents",linkClassName:null}))}},3743:(e,t,n)=>{n.d(t,{Z:()=>g});var a=n(7462),r=n(7294),i=n(6668);function o(e){const t=e.map((e=>({...e,parentIndex:-1,children:[]}))),n=Array(7).fill(-1);t.forEach(((e,t)=>{const a=n.slice(2,e.level);e.parentIndex=Math.max(...a),n[e.level]=t}));const a=[];return t.forEach((e=>{const{parentIndex:n,...r}=e;n>=0?t[n].children.push(r):a.push(r)})),a}function l(e){let{toc:t,minHeadingLevel:n,maxHeadingLevel:a}=e;return t.flatMap((e=>{const t=l({toc:e.children,minHeadingLevel:n,maxHeadingLevel:a});return function(e){return e.level>=n&&e.level<=a}(e)?[{...e,children:t}]:t}))}function s(e){const t=e.getBoundingClientRect();return t.top===t.bottom?s(e.parentNode):t}function c(e,t){var n;let{anchorTopOffset:a}=t;const r=e.find((e=>s(e).top>=a));if(r){var i;return function(e){return e.top>0&&e.bottom{e.current=t?0:document.querySelector(".navbar").clientHeight}),[t]),e}function p(e){const t=(0,r.useRef)(void 0),n=u();(0,r.useEffect)((()=>{if(!e)return()=>{};const{linkClassName:a,linkActiveClassName:r,minHeadingLevel:i,maxHeadingLevel:o}=e;function l(){const e=function(e){return Array.from(document.getElementsByClassName(e))}(a),l=function(e){let{minHeadingLevel:t,maxHeadingLevel:n}=e;const a=[];for(let r=t;r<=n;r+=1)a.push("h"+r+".anchor");return Array.from(document.querySelectorAll(a.join()))}({minHeadingLevel:i,maxHeadingLevel:o}),s=c(l,{anchorTopOffset:n.current}),u=e.find((e=>s&&s.id===function(e){return decodeURIComponent(e.href.substring(e.href.indexOf("#")+1))}(e)));e.forEach((e=>{!function(e,n){n?(t.current&&t.current!==e&&t.current.classList.remove(r),e.classList.add(r),t.current=e):e.classList.remove(r)}(e,e===u)}))}return document.addEventListener("scroll",l),document.addEventListener("resize",l),l(),()=>{document.removeEventListener("scroll",l),document.removeEventListener("resize",l)}}),[e,n])}function m(e){let{toc:t,className:n,linkClassName:a,isChild:i}=e;return t.length?r.createElement("ul",{className:i?void 0:n},t.map((e=>r.createElement("li",{key:e.id},r.createElement("a",{href:"#"+e.id,className:null!=a?a:void 0,dangerouslySetInnerHTML:{__html:e.value}}),r.createElement(m,{isChild:!0,toc:e.children,className:n,linkClassName:a}))))):null}const d=r.memo(m);function g(e){let{toc:t,className:n="table-of-contents table-of-contents__left-border",linkClassName:s="table-of-contents__link",linkActiveClassName:c,minHeadingLevel:u,maxHeadingLevel:m,...g}=e;const f=(0,i.L)(),h=null!=u?u:f.tableOfContents.minHeadingLevel,v=null!=m?m:f.tableOfContents.maxHeadingLevel,k=function(e){let{toc:t,minHeadingLevel:n,maxHeadingLevel:a}=e;return(0,r.useMemo)((()=>l({toc:o(t),minHeadingLevel:n,maxHeadingLevel:a})),[t,n,a])}({toc:t,minHeadingLevel:h,maxHeadingLevel:v});return p((0,r.useMemo)((()=>{if(s&&c)return{linkClassName:s,linkActiveClassName:c,minHeadingLevel:h,maxHeadingLevel:v}}),[s,c,h,v])),r.createElement(d,(0,a.Z)({toc:k,className:n,linkClassName:s},g))}},5374:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>l,default:()=>d,frontMatter:()=>o,metadata:()=>s,toc:()=>u});var a=n(7462),r=(n(7294),n(3905)),i=n(3901);const o={id:"troubleshooting",title:"Troubleshooting"},l=void 0,s={unversionedId:"troubleshooting",id:"troubleshooting",title:"Troubleshooting",description:"This guide describes common issues found by users when integrating React Native Test Library to their projects:",source:"@site/docs/Troubleshooting.md",sourceDirName:".",slug:"/troubleshooting",permalink:"/react-native-testing-library/docs/troubleshooting",draft:!1,editUrl:"https://github.com/callstack/react-native-testing-library/blob/main/website/docs/Troubleshooting.md",tags:[],version:"current",frontMatter:{id:"troubleshooting",title:"Troubleshooting"},sidebar:"docs",previous:{title:"ESLint Plugin Testing Library Compatibility",permalink:"/react-native-testing-library/docs/eslint-plugin-testing-library"},next:{title:"Testing Environment",permalink:"/react-native-testing-library/docs/testing-env"}},c={},u=[{value:"Example repository",id:"example-repository",level:2},{value:"Undefined component error",id:"undefined-component-error",level:2},{value:"Mocking React Native",id:"mocking-react-native",level:3},{value:"Act warnings",id:"act-warnings",level:2}],p={toc:u},m="wrapper";function d(e){let{components:t,...n}=e;return(0,r.kt)(m,(0,a.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"This guide describes common issues found by users when integrating React Native Test Library to their projects:"),(0,r.kt)(i.Z,{toc:u,mdxType:"TOCInline"}),"## Matching React Native, React & React Test Renderer versions",(0,r.kt)("p",null,"Check that you have matching versions of core dependencies:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"React Native"),(0,r.kt)("li",{parentName:"ul"},"React"),(0,r.kt)("li",{parentName:"ul"},"React Test Renderer")),(0,r.kt)("p",null,"React Native uses different versioning scheme from React, you can use ",(0,r.kt)("a",{parentName:"p",href:"https://react-native-community.github.io/upgrade-helper/"},"React Native Upgrade Helper")," to find the correct matching between React Native & React versions. In case you use Expo, you should use dependency versions recommended by them and set by ",(0,r.kt)("inlineCode",{parentName:"p"},"expo upgrade")," command."),(0,r.kt)("p",null,"React Test Renderer usually has same major & minor version as React, as they are closely related and React Test Renderer is part of ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/facebook/react"},"React monorepo"),"."),(0,r.kt)("p",null,"Related issues: ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/callstack/react-native-testing-library/issues/1061"},"#1061"),", ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/callstack/react-native-testing-library/issues/938"},"#938"),", ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/callstack/react-native-testing-library/issues/920"},"#920")),(0,r.kt)("p",null,"Errors that might indicate that you are facing this issue:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"TypeError: Cannot read property 'current' of undefined")," when calling ",(0,r.kt)("inlineCode",{parentName:"li"},"render()")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"TypeError: Cannot read property 'isBatchingLegacy' of undefined")," when calling ",(0,r.kt)("inlineCode",{parentName:"li"},"render()"))),(0,r.kt)("h2",{id:"example-repository"},"Example repository"),(0,r.kt)("p",null,"We maintain an ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/callstack/react-native-testing-library/tree/main/examples/basic"},"example repository")," that showcases a modern React Native Testing Library setup with TypeScript, etc."),(0,r.kt)("p",null,"In case something does not work in your setup you can refer to this repository for recommended configuration."),(0,r.kt)("h2",{id:"undefined-component-error"},"Undefined component error"),(0,r.kt)("blockquote",null,(0,r.kt)("p",{parentName:"blockquote"},"Warning: React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined.")),(0,r.kt)("p",null,"This frequently happens when you mock a complex module incorrectly, e.g.:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"jest.mock('@react-navigation/native', () => {\n return {\n useNavigation: jest.fn(),\n };\n});\n")),(0,r.kt)("p",null,"The above mock will mock ",(0,r.kt)("inlineCode",{parentName:"p"},"useNavigation")," hook as intended, but at the same time all other exports from ",(0,r.kt)("inlineCode",{parentName:"p"},"@react-navigation/native")," package are now ",(0,r.kt)("inlineCode",{parentName:"p"},"undefined"),". If you want to use ",(0,r.kt)("inlineCode",{parentName:"p"},"NavigationContainer")," component from the same package it will be ",(0,r.kt)("inlineCode",{parentName:"p"},"undefined")," and result in the error above."),(0,r.kt)("p",null,"In order to mock only a part of given package you should re-export all other exports using ",(0,r.kt)("inlineCode",{parentName:"p"},"jest.requireActual")," helper:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"jest.mock('@react-navigation/native', () => {\n return {\n ...jest.requireActual('@react-navigation/native'),\n useNavigation: jest.fn(),\n };\n});\n")),(0,r.kt)("p",null,"That way the mock will re-export all of the ",(0,r.kt)("inlineCode",{parentName:"p"},"@react-navigation/native")," members and overwrite only the ",(0,r.kt)("inlineCode",{parentName:"p"},"useNavigation")," hook."),(0,r.kt)("p",null,"Alternatively, you can use ",(0,r.kt)("inlineCode",{parentName:"p"},"jest.spyOn")," to mock package exports selectively."),(0,r.kt)("h3",{id:"mocking-react-native"},"Mocking React Native"),(0,r.kt)("p",null,"In case of mocking ",(0,r.kt)("inlineCode",{parentName:"p"},"react-native")," package you should not mock the whole package at once, as this approach has issues with ",(0,r.kt)("inlineCode",{parentName:"p"},"jest.requireActual")," call. In this case it is recommended to mock particular library paths inside the package, e.g.:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter');\n")),(0,r.kt)("h2",{id:"act-warnings"},"Act warnings"),(0,r.kt)("p",null,"When writing tests you may encounter warnings connected with ",(0,r.kt)("inlineCode",{parentName:"p"},"act()")," function. There are two kinds of these warnings:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"sync ",(0,r.kt)("inlineCode",{parentName:"li"},"act()")," warning - ",(0,r.kt)("inlineCode",{parentName:"li"},"Warning: An update to Component inside a test was not wrapped in act(...)")),(0,r.kt)("li",{parentName:"ul"},"async ",(0,r.kt)("inlineCode",{parentName:"li"},"act()")," warning - ",(0,r.kt)("inlineCode",{parentName:"li"},"Warning: You called act(async () => ...) without await"))),(0,r.kt)("p",null,"You can read more about ",(0,r.kt)("inlineCode",{parentName:"p"},"act()")," function in our ",(0,r.kt)("a",{parentName:"p",href:"https://callstack.github.io/react-native-testing-library/docs/understanding-act"},"understanding ",(0,r.kt)("inlineCode",{parentName:"a"},"act")," function guide"),"."),(0,r.kt)("p",null,"Normally, you should not encounter sync ",(0,r.kt)("inlineCode",{parentName:"p"},"act()")," warnings, but if that happens this probably indicate an issue with your test and should be investigated."),(0,r.kt)("p",null,"In case of async ",(0,r.kt)("inlineCode",{parentName:"p"},"act()")," function this might happen more or less randomly, especially if your components contain async logic. So far this warning does not seem to affect test correctness."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/14f61f32.29a68f7a.js b/assets/js/14f61f32.f5d2aaf6.js similarity index 84% rename from assets/js/14f61f32.29a68f7a.js rename to assets/js/14f61f32.f5d2aaf6.js index 099ff78b..72dbcaa2 100644 --- a/assets/js/14f61f32.29a68f7a.js +++ b/assets/js/14f61f32.f5d2aaf6.js @@ -1 +1 @@ -"use strict";(self.webpackChunkreact_native_testing_library_website=self.webpackChunkreact_native_testing_library_website||[]).push([[456],{3905:(e,t,n)=>{n.d(t,{Zo:()=>u,kt:()=>h});var i=n(7294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,i)}return n}function l(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var s=i.createContext({}),c=function(e){var t=i.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},u=function(e){var t=c(e.components);return i.createElement(s.Provider,{value:t},e.children)},m="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return i.createElement(i.Fragment,{},t)}},p=i.forwardRef((function(e,t){var n=e.components,a=e.mdxType,r=e.originalType,s=e.parentName,u=o(e,["components","mdxType","originalType","parentName"]),m=c(n),p=a,h=m["".concat(s,".").concat(p)]||m[p]||d[p]||r;return n?i.createElement(h,l(l({ref:t},u),{},{components:n})):i.createElement(h,l({ref:t},u))}));function h(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var r=n.length,l=new Array(r);l[0]=p;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o[m]="string"==typeof e?e:a,l[1]=o;for(var c=2;c{n.d(t,{Z:()=>l});var i=n(7294),a=n(3743);const r={tableOfContentsInline:"tableOfContentsInline_prmo"};function l(e){let{toc:t,minHeadingLevel:n,maxHeadingLevel:l}=e;return i.createElement("div",{className:r.tableOfContentsInline},i.createElement(a.Z,{toc:t,minHeadingLevel:n,maxHeadingLevel:l,className:"table-of-contents",linkClassName:null}))}},3743:(e,t,n)=>{n.d(t,{Z:()=>h});var i=n(7462),a=n(7294),r=n(6668);function l(e){const t=e.map((e=>({...e,parentIndex:-1,children:[]}))),n=Array(7).fill(-1);t.forEach(((e,t)=>{const i=n.slice(2,e.level);e.parentIndex=Math.max(...i),n[e.level]=t}));const i=[];return t.forEach((e=>{const{parentIndex:n,...a}=e;n>=0?t[n].children.push(a):i.push(a)})),i}function o(e){let{toc:t,minHeadingLevel:n,maxHeadingLevel:i}=e;return t.flatMap((e=>{const t=o({toc:e.children,minHeadingLevel:n,maxHeadingLevel:i});return function(e){return e.level>=n&&e.level<=i}(e)?[{...e,children:t}]:t}))}function s(e){const t=e.getBoundingClientRect();return t.top===t.bottom?s(e.parentNode):t}function c(e,t){var n;let{anchorTopOffset:i}=t;const a=e.find((e=>s(e).top>=i));if(a){var r;return function(e){return e.top>0&&e.bottom{e.current=t?0:document.querySelector(".navbar").clientHeight}),[t]),e}function m(e){const t=(0,a.useRef)(void 0),n=u();(0,a.useEffect)((()=>{if(!e)return()=>{};const{linkClassName:i,linkActiveClassName:a,minHeadingLevel:r,maxHeadingLevel:l}=e;function o(){const e=function(e){return Array.from(document.getElementsByClassName(e))}(i),o=function(e){let{minHeadingLevel:t,maxHeadingLevel:n}=e;const i=[];for(let a=t;a<=n;a+=1)i.push("h"+a+".anchor");return Array.from(document.querySelectorAll(i.join()))}({minHeadingLevel:r,maxHeadingLevel:l}),s=c(o,{anchorTopOffset:n.current}),u=e.find((e=>s&&s.id===function(e){return decodeURIComponent(e.href.substring(e.href.indexOf("#")+1))}(e)));e.forEach((e=>{!function(e,n){n?(t.current&&t.current!==e&&t.current.classList.remove(a),e.classList.add(a),t.current=e):e.classList.remove(a)}(e,e===u)}))}return document.addEventListener("scroll",o),document.addEventListener("resize",o),o(),()=>{document.removeEventListener("scroll",o),document.removeEventListener("resize",o)}}),[e,n])}function d(e){let{toc:t,className:n,linkClassName:i,isChild:r}=e;return t.length?a.createElement("ul",{className:r?void 0:n},t.map((e=>a.createElement("li",{key:e.id},a.createElement("a",{href:"#"+e.id,className:null!=i?i:void 0,dangerouslySetInnerHTML:{__html:e.value}}),a.createElement(d,{isChild:!0,toc:e.children,className:n,linkClassName:i}))))):null}const p=a.memo(d);function h(e){let{toc:t,className:n="table-of-contents table-of-contents__left-border",linkClassName:s="table-of-contents__link",linkActiveClassName:c,minHeadingLevel:u,maxHeadingLevel:d,...h}=e;const f=(0,r.L)(),g=null!=u?u:f.tableOfContents.minHeadingLevel,v=null!=d?d:f.tableOfContents.maxHeadingLevel,y=function(e){let{toc:t,minHeadingLevel:n,maxHeadingLevel:i}=e;return(0,a.useMemo)((()=>o({toc:l(t),minHeadingLevel:n,maxHeadingLevel:i})),[t,n,i])}({toc:t,minHeadingLevel:g,maxHeadingLevel:v});return m((0,a.useMemo)((()=>{if(s&&c)return{linkClassName:s,linkActiveClassName:c,minHeadingLevel:g,maxHeadingLevel:v}}),[s,c,g,v])),a.createElement(p,(0,i.Z)({toc:y,className:n,linkClassName:s},h))}},2087:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>o,default:()=>p,frontMatter:()=>l,metadata:()=>s,toc:()=>u});var i=n(7462),a=(n(7294),n(3905)),r=n(3901);const l={id:"migration-v12",title:"Migration to 12.0"},o=void 0,s={unversionedId:"migration-v12",id:"migration-v12",title:"Migration to 12.0",description:"From v12.4:",source:"@site/docs/MigrationV12.md",sourceDirName:".",slug:"/migration-v12",permalink:"/react-native-testing-library/docs/migration-v12",draft:!1,editUrl:"https://github.com/callstack/react-native-testing-library/blob/main/website/docs/MigrationV12.md",tags:[],version:"current",frontMatter:{id:"migration-v12",title:"Migration to 12.0"},sidebar:"docs",previous:{title:"Migration from Jest Native matchers",permalink:"/react-native-testing-library/docs/migration-jest-native"},next:{title:"Migration to 11.0",permalink:"/react-native-testing-library/docs/migration-v11"}},c={},u=[{value:"Breaking changes",id:"breaking-changes",level:2},{value:"1. All queries exclude elements hidden from accessibility by default",id:"1-all-queries-exclude-elements-hidden-from-accessibility-by-default",level:3},{value:"2. *ByRole queries now return only accessibility elements",id:"2-byrole-queries-now-return-only-accessibility-elements",level:3},{value:"Examples",id:"examples",level:4},{value:"3. *ByText, *ByDisplayValue, *ByPlaceholderText queries now return host elements",id:"3-bytext-bydisplayvalue-byplaceholdertext-queries-now-return-host-elements",level:3},{value:"4. container API has been renamed to UNSAFE_root.",id:"4-container-api-has-been-renamed-to-unsafe_root",level:3},{value:"Full Changelog",id:"full-changelog",level:2}],m={toc:u},d="wrapper";function p(e){let{components:t,...n}=e;return(0,a.kt)(d,(0,i.Z)({},m,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("admonition",{type:"note"},(0,a.kt)("p",{parentName:"admonition"},"From v12.4:"),(0,a.kt)("p",{parentName:"admonition"},"If you are already using legacy Jest Native matchers we have a ",(0,a.kt)("a",{parentName:"p",href:"migration-jest-native"},"migration guide")," for moving to the built-in matchers."),(0,a.kt)("p",{parentName:"admonition"},"Before v12.4:\nIf you use ",(0,a.kt)("a",{parentName:"p",href:"https://github.com/testing-library/jest-native"},"Jest Native matchers"),", which we recommend, then you should upgrade it to version 5.4.2 or higher.")),(0,a.kt)("p",null,"React Native Testing Library 12 introduces a handful of breaking changes compared to 11.x versions. We believe they were necessary to improve the experience using the library and help the users ",(0,a.kt)("a",{parentName:"p",href:"https://blog.codinghorror.com/falling-into-the-pit-of-success/"},"fall into the pit of success")," when writing meaningful tests. You will find migration instructions for each and every change described below."),(0,a.kt)(r.Z,{toc:u,mdxType:"TOCInline"}),(0,a.kt)("h2",{id:"breaking-changes"},"Breaking changes"),(0,a.kt)("h3",{id:"1-all-queries-exclude-elements-hidden-from-accessibility-by-default"},"1. All queries exclude elements hidden from accessibility by default"),(0,a.kt)("p",null,"Elements that are hidden from accessiblity, e.g. elements on non-active screen when using React Navigation, now will not be matched by default by all queries. This is the effect of switching the default value for global config option ",(0,a.kt)("inlineCode",{parentName:"p"},"defaultIncludeHiddenElements"),"(api#defaultincludehiddenelements-option) to ",(0,a.kt)("inlineCode",{parentName:"p"},"false"),"."),(0,a.kt)("p",null,"Previous behaviour of matching hidden elements can be enabled on query level using ",(0,a.kt)("a",{parentName:"p",href:"api-queries#includehiddenelements-option"},"includeHiddenElements")," query options or globally using ",(0,a.kt)("inlineCode",{parentName:"p"},"defaultIncludeHiddenElements"),"(api#defaultincludehiddenelements-option) configuration option."),(0,a.kt)("h3",{id:"2-byrole-queries-now-return-only-accessibility-elements"},"2. ",(0,a.kt)("inlineCode",{parentName:"h3"},"*ByRole")," queries now return only accessibility elements"),(0,a.kt)("p",null,(0,a.kt)("inlineCode",{parentName:"p"},"*ByRole")," queries now return only accessibility elements, either explicitly marked with ",(0,a.kt)("inlineCode",{parentName:"p"},"accessible")," prop or implicit ones where this status is derived from component type itself (e.g ",(0,a.kt)("inlineCode",{parentName:"p"},"Text"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"TextInput"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"Switch"),", but not ",(0,a.kt)("inlineCode",{parentName:"p"},"View"),")."),(0,a.kt)("p",null,"You may need to adjust relevant components under test to make sure they pass ",(0,a.kt)("inlineCode",{parentName:"p"},"isAccessibilityElement")," check."),(0,a.kt)("h4",{id:"examples"},"Examples"),(0,a.kt)("p",null,"Let's assume we are using ",(0,a.kt)("inlineCode",{parentName:"p"},'getByRole("button")')," query."),(0,a.kt)("p",null,"Following elements will match:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-ts"},'// Explicit "accessible" prop for View\n\n\n// No need to "accessible" prop for Text, as it is implicitly accessible element.\nButton\n')),(0,a.kt)("p",null,"While following elements will not match:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-ts"},'// Missing "accessible" prop for View\n\n\n// Explicit "accessible={false}" prop for View\n\n\n// Explicit "accessible={false}" for Text, which is implicitly accessible element\nButton\n')),(0,a.kt)("h3",{id:"3-bytext-bydisplayvalue-byplaceholdertext-queries-now-return-host-elements"},"3. ",(0,a.kt)("inlineCode",{parentName:"h3"},"*ByText"),", ",(0,a.kt)("inlineCode",{parentName:"h3"},"*ByDisplayValue"),", ",(0,a.kt)("inlineCode",{parentName:"h3"},"*ByPlaceholderText")," queries now return host elements"),(0,a.kt)("p",null,(0,a.kt)("inlineCode",{parentName:"p"},"*ByText"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"*ByDisplayValue"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"*ByPlaceholderText")," queries now return ",(0,a.kt)("a",{parentName:"p",href:"testing-env#host-and-composite-components"},"host elements"),", which is consistent with other queries."),(0,a.kt)("p",null,"While potentially breaking, this should not cause issues in tests if you are using recommended queries and Jest Matchers from Jest Native package. "),(0,a.kt)("p",null,"Problematic cases may include: directly checking some prop values (without using Jest Native matchers), referencing other nodes using ",(0,a.kt)("inlineCode",{parentName:"p"},"parent")," or ",(0,a.kt)("inlineCode",{parentName:"p"},"children")," props, examining ",(0,a.kt)("inlineCode",{parentName:"p"},"type")," property of ",(0,a.kt)("inlineCode",{parentName:"p"},"ReactTestInstance"),", etc."),(0,a.kt)("h3",{id:"4-container-api-has-been-renamed-to-unsafe_root"},"4. ",(0,a.kt)("inlineCode",{parentName:"h3"},"container")," API has been renamed to ",(0,a.kt)("inlineCode",{parentName:"h3"},"UNSAFE_root"),"."),(0,a.kt)("p",null,"Historically ",(0,a.kt)("inlineCode",{parentName:"p"},"container")," was supposed to mimic the ",(0,a.kt)("a",{parentName:"p",href:"https://testing-library.com/docs/react-testing-library/api/#container"},"RTL's container"),". However it turned out not so relevant in RNTL's environment, where we actually used it to return React Test Renderer's root instance."),(0,a.kt)("p",null,"RNTL v12 introduces ",(0,a.kt)("inlineCode",{parentName:"p"},"root")," API as an alternative that returns a root ",(0,a.kt)("strong",{parentName:"p"},"host")," element. The difference between ",(0,a.kt)("inlineCode",{parentName:"p"},"root")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"UNSAFE_root")," properties is that that ",(0,a.kt)("inlineCode",{parentName:"p"},"root")," will always represents a host element, while ",(0,a.kt)("inlineCode",{parentName:"p"},"UNSAFE_root")," will typically represent a composite element."),(0,a.kt)("p",null,"If you use ",(0,a.kt)("inlineCode",{parentName:"p"},"toBeOnTheScreen")," matcher from ",(0,a.kt)("a",{parentName:"p",href:"https://github.com/testing-library/jest-native"},"@testing-library/jest-native")," your tests will fail because it uses the ",(0,a.kt)("inlineCode",{parentName:"p"},"container")," api. To fix this, update ",(0,a.kt)("inlineCode",{parentName:"p"},"@testing-library/jest-native")," to version 5.4.2. "),(0,a.kt)("h2",{id:"full-changelog"},"Full Changelog"),(0,a.kt)("p",null,(0,a.kt)("a",{parentName:"p",href:"https://github.com/callstack/react-native-testing-library/compare/v11.5.2...v12.0.0"},"https://github.com/callstack/react-native-testing-library/compare/v11.5.2...v12.0.0")))}p.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkreact_native_testing_library_website=self.webpackChunkreact_native_testing_library_website||[]).push([[456],{3905:(e,t,n)=>{n.d(t,{Zo:()=>u,kt:()=>h});var i=n(7294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,i)}return n}function l(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var s=i.createContext({}),c=function(e){var t=i.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},u=function(e){var t=c(e.components);return i.createElement(s.Provider,{value:t},e.children)},m="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return i.createElement(i.Fragment,{},t)}},p=i.forwardRef((function(e,t){var n=e.components,a=e.mdxType,r=e.originalType,s=e.parentName,u=o(e,["components","mdxType","originalType","parentName"]),m=c(n),p=a,h=m["".concat(s,".").concat(p)]||m[p]||d[p]||r;return n?i.createElement(h,l(l({ref:t},u),{},{components:n})):i.createElement(h,l({ref:t},u))}));function h(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var r=n.length,l=new Array(r);l[0]=p;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o[m]="string"==typeof e?e:a,l[1]=o;for(var c=2;c{n.d(t,{Z:()=>l});var i=n(7294),a=n(3743);const r={tableOfContentsInline:"tableOfContentsInline_prmo"};function l(e){let{toc:t,minHeadingLevel:n,maxHeadingLevel:l}=e;return i.createElement("div",{className:r.tableOfContentsInline},i.createElement(a.Z,{toc:t,minHeadingLevel:n,maxHeadingLevel:l,className:"table-of-contents",linkClassName:null}))}},3743:(e,t,n)=>{n.d(t,{Z:()=>h});var i=n(7462),a=n(7294),r=n(6668);function l(e){const t=e.map((e=>({...e,parentIndex:-1,children:[]}))),n=Array(7).fill(-1);t.forEach(((e,t)=>{const i=n.slice(2,e.level);e.parentIndex=Math.max(...i),n[e.level]=t}));const i=[];return t.forEach((e=>{const{parentIndex:n,...a}=e;n>=0?t[n].children.push(a):i.push(a)})),i}function o(e){let{toc:t,minHeadingLevel:n,maxHeadingLevel:i}=e;return t.flatMap((e=>{const t=o({toc:e.children,minHeadingLevel:n,maxHeadingLevel:i});return function(e){return e.level>=n&&e.level<=i}(e)?[{...e,children:t}]:t}))}function s(e){const t=e.getBoundingClientRect();return t.top===t.bottom?s(e.parentNode):t}function c(e,t){var n;let{anchorTopOffset:i}=t;const a=e.find((e=>s(e).top>=i));if(a){var r;return function(e){return e.top>0&&e.bottom{e.current=t?0:document.querySelector(".navbar").clientHeight}),[t]),e}function m(e){const t=(0,a.useRef)(void 0),n=u();(0,a.useEffect)((()=>{if(!e)return()=>{};const{linkClassName:i,linkActiveClassName:a,minHeadingLevel:r,maxHeadingLevel:l}=e;function o(){const e=function(e){return Array.from(document.getElementsByClassName(e))}(i),o=function(e){let{minHeadingLevel:t,maxHeadingLevel:n}=e;const i=[];for(let a=t;a<=n;a+=1)i.push("h"+a+".anchor");return Array.from(document.querySelectorAll(i.join()))}({minHeadingLevel:r,maxHeadingLevel:l}),s=c(o,{anchorTopOffset:n.current}),u=e.find((e=>s&&s.id===function(e){return decodeURIComponent(e.href.substring(e.href.indexOf("#")+1))}(e)));e.forEach((e=>{!function(e,n){n?(t.current&&t.current!==e&&t.current.classList.remove(a),e.classList.add(a),t.current=e):e.classList.remove(a)}(e,e===u)}))}return document.addEventListener("scroll",o),document.addEventListener("resize",o),o(),()=>{document.removeEventListener("scroll",o),document.removeEventListener("resize",o)}}),[e,n])}function d(e){let{toc:t,className:n,linkClassName:i,isChild:r}=e;return t.length?a.createElement("ul",{className:r?void 0:n},t.map((e=>a.createElement("li",{key:e.id},a.createElement("a",{href:"#"+e.id,className:null!=i?i:void 0,dangerouslySetInnerHTML:{__html:e.value}}),a.createElement(d,{isChild:!0,toc:e.children,className:n,linkClassName:i}))))):null}const p=a.memo(d);function h(e){let{toc:t,className:n="table-of-contents table-of-contents__left-border",linkClassName:s="table-of-contents__link",linkActiveClassName:c,minHeadingLevel:u,maxHeadingLevel:d,...h}=e;const f=(0,r.L)(),g=null!=u?u:f.tableOfContents.minHeadingLevel,v=null!=d?d:f.tableOfContents.maxHeadingLevel,y=function(e){let{toc:t,minHeadingLevel:n,maxHeadingLevel:i}=e;return(0,a.useMemo)((()=>o({toc:l(t),minHeadingLevel:n,maxHeadingLevel:i})),[t,n,i])}({toc:t,minHeadingLevel:g,maxHeadingLevel:v});return m((0,a.useMemo)((()=>{if(s&&c)return{linkClassName:s,linkActiveClassName:c,minHeadingLevel:g,maxHeadingLevel:v}}),[s,c,g,v])),a.createElement(p,(0,i.Z)({toc:y,className:n,linkClassName:s},h))}},2087:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>o,default:()=>p,frontMatter:()=>l,metadata:()=>s,toc:()=>u});var i=n(7462),a=(n(7294),n(3905)),r=n(3901);const l={id:"migration-v12",title:"Migration to 12.0"},o=void 0,s={unversionedId:"migration-v12",id:"migration-v12",title:"Migration to 12.0",description:"From v12.4:",source:"@site/docs/MigrationV12.md",sourceDirName:".",slug:"/migration-v12",permalink:"/react-native-testing-library/docs/migration-v12",draft:!1,editUrl:"https://github.com/callstack/react-native-testing-library/blob/main/website/docs/MigrationV12.md",tags:[],version:"current",frontMatter:{id:"migration-v12",title:"Migration to 12.0"},sidebar:"docs",previous:{title:"Migration from Jest Native matchers",permalink:"/react-native-testing-library/docs/migration-jest-native"},next:{title:"Migration to 11.0",permalink:"/react-native-testing-library/docs/migration-v11"}},c={},u=[{value:"Breaking changes",id:"breaking-changes",level:2},{value:"1. All queries exclude elements hidden from accessibility by default",id:"1-all-queries-exclude-elements-hidden-from-accessibility-by-default",level:3},{value:"2. *ByRole queries now return only accessibility elements",id:"2-byrole-queries-now-return-only-accessibility-elements",level:3},{value:"Examples",id:"examples",level:4},{value:"3. *ByText, *ByDisplayValue, *ByPlaceholderText queries now return host elements",id:"3-bytext-bydisplayvalue-byplaceholdertext-queries-now-return-host-elements",level:3},{value:"4. container API has been renamed to UNSAFE_root.",id:"4-container-api-has-been-renamed-to-unsafe_root",level:3},{value:"Full Changelog",id:"full-changelog",level:2}],m={toc:u},d="wrapper";function p(e){let{components:t,...n}=e;return(0,a.kt)(d,(0,i.Z)({},m,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("admonition",{type:"note"},(0,a.kt)("p",{parentName:"admonition"},"From v12.4:"),(0,a.kt)("p",{parentName:"admonition"},"If you are already using legacy Jest Native matchers we have a ",(0,a.kt)("a",{parentName:"p",href:"migration-jest-native"},"migration guide")," for moving to the built-in matchers."),(0,a.kt)("p",{parentName:"admonition"},"Before v12.4:\nIf you use ",(0,a.kt)("a",{parentName:"p",href:"https://github.com/testing-library/jest-native"},"Jest Native matchers"),", which we recommend, then you should upgrade it to version 5.4.2 or higher.")),(0,a.kt)("p",null,"React Native Testing Library 12 introduces a handful of breaking changes compared to 11.x versions. We believe they were necessary to improve the experience using the library and help the users ",(0,a.kt)("a",{parentName:"p",href:"https://blog.codinghorror.com/falling-into-the-pit-of-success/"},"fall into the pit of success")," when writing meaningful tests. You will find migration instructions for each and every change described below."),(0,a.kt)(r.Z,{toc:u,mdxType:"TOCInline"}),(0,a.kt)("h2",{id:"breaking-changes"},"Breaking changes"),(0,a.kt)("h3",{id:"1-all-queries-exclude-elements-hidden-from-accessibility-by-default"},"1. All queries exclude elements hidden from accessibility by default"),(0,a.kt)("p",null,"Elements that are hidden from accessiblity, e.g. elements on non-active screen when using React Navigation, now will not be matched by default by all queries. This is the effect of switching the default value for global config option ",(0,a.kt)("inlineCode",{parentName:"p"},"defaultIncludeHiddenElements"),"(api#defaultincludehiddenelements-option) to ",(0,a.kt)("inlineCode",{parentName:"p"},"false"),"."),(0,a.kt)("p",null,"Previous behaviour of matching hidden elements can be enabled on query level using ",(0,a.kt)("a",{parentName:"p",href:"api-queries#includehiddenelements-option"},"includeHiddenElements")," query options or globally using ",(0,a.kt)("inlineCode",{parentName:"p"},"defaultIncludeHiddenElements"),"(api#defaultincludehiddenelements-option) configuration option."),(0,a.kt)("h3",{id:"2-byrole-queries-now-return-only-accessibility-elements"},"2. ",(0,a.kt)("inlineCode",{parentName:"h3"},"*ByRole")," queries now return only accessibility elements"),(0,a.kt)("p",null,(0,a.kt)("inlineCode",{parentName:"p"},"*ByRole")," queries now return only accessibility elements, either explicitly marked with ",(0,a.kt)("inlineCode",{parentName:"p"},"accessible")," prop or implicit ones where this status is derived from component type itself (e.g ",(0,a.kt)("inlineCode",{parentName:"p"},"Text"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"TextInput"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"Switch"),", but not ",(0,a.kt)("inlineCode",{parentName:"p"},"View"),")."),(0,a.kt)("p",null,"You may need to adjust relevant components under test to make sure they pass ",(0,a.kt)("inlineCode",{parentName:"p"},"isAccessibilityElement")," check."),(0,a.kt)("h4",{id:"examples"},"Examples"),(0,a.kt)("p",null,"Let's assume we are using ",(0,a.kt)("inlineCode",{parentName:"p"},'getByRole("button")')," query."),(0,a.kt)("p",null,"Following elements will match:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-ts"},'// Explicit "accessible" prop for View\n\n\n// No need to "accessible" prop for Text, as it is implicitly accessible element.\nButton\n')),(0,a.kt)("p",null,"While following elements will not match:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-ts"},'// Missing "accessible" prop for View\n\n\n// Explicit "accessible={false}" prop for View\n\n\n// Explicit "accessible={false}" for Text, which is implicitly accessible element\nButton\n')),(0,a.kt)("h3",{id:"3-bytext-bydisplayvalue-byplaceholdertext-queries-now-return-host-elements"},"3. ",(0,a.kt)("inlineCode",{parentName:"h3"},"*ByText"),", ",(0,a.kt)("inlineCode",{parentName:"h3"},"*ByDisplayValue"),", ",(0,a.kt)("inlineCode",{parentName:"h3"},"*ByPlaceholderText")," queries now return host elements"),(0,a.kt)("p",null,(0,a.kt)("inlineCode",{parentName:"p"},"*ByText"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"*ByDisplayValue"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"*ByPlaceholderText")," queries now return ",(0,a.kt)("a",{parentName:"p",href:"testing-env#host-and-composite-components"},"host elements"),", which is consistent with other queries."),(0,a.kt)("p",null,"While potentially breaking, this should not cause issues in tests if you are using recommended queries and Jest Matchers from Jest Native package."),(0,a.kt)("p",null,"Problematic cases may include: directly checking some prop values (without using Jest Native matchers), referencing other nodes using ",(0,a.kt)("inlineCode",{parentName:"p"},"parent")," or ",(0,a.kt)("inlineCode",{parentName:"p"},"children")," props, examining ",(0,a.kt)("inlineCode",{parentName:"p"},"type")," property of ",(0,a.kt)("inlineCode",{parentName:"p"},"ReactTestInstance"),", etc."),(0,a.kt)("h3",{id:"4-container-api-has-been-renamed-to-unsafe_root"},"4. ",(0,a.kt)("inlineCode",{parentName:"h3"},"container")," API has been renamed to ",(0,a.kt)("inlineCode",{parentName:"h3"},"UNSAFE_root"),"."),(0,a.kt)("p",null,"Historically ",(0,a.kt)("inlineCode",{parentName:"p"},"container")," was supposed to mimic the ",(0,a.kt)("a",{parentName:"p",href:"https://testing-library.com/docs/react-testing-library/api/#container"},"RTL's container"),". However it turned out not so relevant in RNTL's environment, where we actually used it to return React Test Renderer's root instance."),(0,a.kt)("p",null,"RNTL v12 introduces ",(0,a.kt)("inlineCode",{parentName:"p"},"root")," API as an alternative that returns a root ",(0,a.kt)("strong",{parentName:"p"},"host")," element. The difference between ",(0,a.kt)("inlineCode",{parentName:"p"},"root")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"UNSAFE_root")," properties is that that ",(0,a.kt)("inlineCode",{parentName:"p"},"root")," will always represents a host element, while ",(0,a.kt)("inlineCode",{parentName:"p"},"UNSAFE_root")," will typically represent a composite element."),(0,a.kt)("p",null,"If you use ",(0,a.kt)("inlineCode",{parentName:"p"},"toBeOnTheScreen")," matcher from ",(0,a.kt)("a",{parentName:"p",href:"https://github.com/testing-library/jest-native"},"@testing-library/jest-native")," your tests will fail because it uses the ",(0,a.kt)("inlineCode",{parentName:"p"},"container")," api. To fix this, update ",(0,a.kt)("inlineCode",{parentName:"p"},"@testing-library/jest-native")," to version 5.4.2."),(0,a.kt)("h2",{id:"full-changelog"},"Full Changelog"),(0,a.kt)("p",null,(0,a.kt)("a",{parentName:"p",href:"https://github.com/callstack/react-native-testing-library/compare/v11.5.2...v12.0.0"},"https://github.com/callstack/react-native-testing-library/compare/v11.5.2...v12.0.0")))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1bdd165f.66a4d1db.js b/assets/js/1bdd165f.66a4d1db.js new file mode 100644 index 00000000..52a47112 --- /dev/null +++ b/assets/js/1bdd165f.66a4d1db.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkreact_native_testing_library_website=self.webpackChunkreact_native_testing_library_website||[]).push([[94],{3905:(e,t,n)=>{n.d(t,{Zo:()=>p,kt:()=>g});var a=n(7294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var c=a.createContext({}),l=function(e){var t=a.useContext(c),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},p=function(e){var t=l(e.components);return a.createElement(c.Provider,{value:t},e.children)},m="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},u=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,r=e.originalType,c=e.parentName,p=s(e,["components","mdxType","originalType","parentName"]),m=l(n),u=i,g=m["".concat(c,".").concat(u)]||m[u]||d[u]||r;return n?a.createElement(g,o(o({ref:t},p),{},{components:n})):a.createElement(g,o({ref:t},p))}));function g(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var r=n.length,o=new Array(r);o[0]=u;var s={};for(var c in t)hasOwnProperty.call(t,c)&&(s[c]=t[c]);s.originalType=e,s[m]="string"==typeof e?e:i,o[1]=s;for(var l=2;l{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>o,default:()=>d,frontMatter:()=>r,metadata:()=>s,toc:()=>l});var a=n(7462),i=(n(7294),n(3905));const r={id:"react-navigation",title:"React Navigation"},o=void 0,s={unversionedId:"react-navigation",id:"react-navigation",title:"React Navigation",description:"This examples has not been updated for a long time and not showcase current recommended testing practices. We plan to update this document soon.",source:"@site/docs/ReactNavigation.md",sourceDirName:".",slug:"/react-navigation",permalink:"/react-native-testing-library/docs/react-navigation",draft:!1,editUrl:"https://github.com/callstack/react-native-testing-library/blob/main/website/docs/ReactNavigation.md",tags:[],version:"current",frontMatter:{id:"react-navigation",title:"React Navigation"},sidebar:"docs",previous:{title:"Migration to 2.0",permalink:"/react-native-testing-library/docs/migration-v2"},next:{title:"Redux Integration",permalink:"/react-native-testing-library/docs/redux-integration"}},c={},l=[{value:"Stack Navigator",id:"stack-navigator",level:2},{value:"Setting up",id:"setting-up",level:3},{value:"Setting up the test environment",id:"setting-up-the-test-environment",level:3},{value:"Example tests",id:"example-tests",level:3},{value:"Drawer Navigator",id:"drawer-navigator",level:2},{value:"Setting up",id:"setting-up-1",level:3},{value:"Setting up the test environment",id:"setting-up-the-test-environment-1",level:3},{value:"Example tests",id:"example-tests-1",level:3},{value:"Running tests",id:"running-tests",level:2}],p={toc:l},m="wrapper";function d(e){let{components:t,...n}=e;return(0,i.kt)(m,(0,a.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("admonition",{type:"caution"},(0,i.kt)("p",{parentName:"admonition"},"This examples has not been updated for a long time and not showcase current recommended testing practices. We plan to update this document soon.")),(0,i.kt)("p",null,"This section deals with integrating ",(0,i.kt)("inlineCode",{parentName:"p"},"@testing-library/react-native")," with ",(0,i.kt)("inlineCode",{parentName:"p"},"react-navigation"),", using Jest."),(0,i.kt)("h2",{id:"stack-navigator"},"Stack Navigator"),(0,i.kt)("h3",{id:"setting-up"},"Setting up"),(0,i.kt)("p",null,"Install the packages required for React Navigation. For this example, we will use a ",(0,i.kt)("a",{parentName:"p",href:"https://reactnavigation.org/docs/stack-navigator/"},"stack navigator")," to transition to the second page when any of the items are clicked on."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"$ yarn add @react-native-community/masked-view @react-navigation/native @react-navigation/stack react-native-gesture-handler react-native-reanimated react-native-safe-area-context react-native-screens\n")),(0,i.kt)("p",null,"Create an ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/callstack/react-native-testing-library/blob/main/examples/reactnavigation/src/AppNavigator.js"},(0,i.kt)("inlineCode",{parentName:"a"},"./AppNavigator.js"))," component which will list the navigation stack:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-jsx"},"import 'react-native-gesture-handler';\nimport * as React from 'react';\nimport { createStackNavigator } from '@react-navigation/stack';\n\nimport HomeScreen from './screens/HomeScreen';\nimport DetailsScreen from './screens/DetailsScreen';\n\nconst { Screen, Navigator } = createStackNavigator();\n\nexport default function Navigation() {\n const options = {};\n\n return (\n \n \n \n \n );\n}\n")),(0,i.kt)("p",null,"Create your two screens which we will transition to and from them. The homescreen, found in ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/callstack/react-native-testing-library/blob/main/examples/reactnavigation/src/screens/HomeScreen.js"},(0,i.kt)("inlineCode",{parentName:"a"},"./screens/HomeScreen.js")),", contains a list of elements presented in a list view. On tap of any of these items will move to the details screen with the item number:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-jsx"},"import * as React from 'react';\nimport { Text, View, FlatList, TouchableOpacity, StyleSheet } from 'react-native';\n\nexport default function HomeScreen({ navigation }) {\n const [items] = React.useState(new Array(20).fill(null).map((_, idx) => idx + 1));\n\n const onOpacityPress = (item) => navigation.navigate('Details', item);\n\n return (\n \n List of numbers from 1 to 20\n `${idx}`}\n data={items}\n renderItem={({ item }) => (\n onOpacityPress(item)} style={styles.row}>\n Item number {item}\n \n )}\n />\n \n );\n}\n\nconst divider = '#DDDDDD';\n\nconst styles = StyleSheet.create({\n header: {\n fontSize: 20,\n textAlign: 'center',\n marginVertical: 16,\n },\n row: {\n paddingVertical: 16,\n paddingHorizontal: 24,\n borderBottomColor: divider,\n borderBottomWidth: 1,\n },\n});\n")),(0,i.kt)("p",null,"The details screen, found in ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/callstack/react-native-testing-library/blob/main/examples/reactnavigation/src/screens/DetailsScreen.js"},(0,i.kt)("inlineCode",{parentName:"a"},"./screens/DetailsScreen.js")),", contains a header with the item number passed from the home screen:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-jsx"},"// ./screens/DetailsScreen.js\nimport * as React from 'react';\nimport { Text, StyleSheet, View } from 'react-native';\n\nexport default function DetailsScreen(props) {\n const item = Number.parseInt(props.route.params, 10);\n\n return (\n \n Showing details for {item}\n the number you have chosen is {item}\n \n );\n}\n\nconst styles = StyleSheet.create({\n header: {\n fontSize: 20,\n textAlign: 'center',\n marginVertical: 16,\n },\n body: {\n textAlign: 'center',\n },\n});\n")),(0,i.kt)("h3",{id:"setting-up-the-test-environment"},"Setting up the test environment"),(0,i.kt)("p",null,"Install required dev dependencies:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"$ yarn add -D jest @testing-library/react-native\n")),(0,i.kt)("p",null,"Create your ",(0,i.kt)("inlineCode",{parentName:"p"},"jest.config.js")," file (or place the following properties in your ",(0,i.kt)("inlineCode",{parentName:"p"},"package.json"),' as a "jest" property)'),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js"},"module.exports = {\n preset: 'react-native',\n setupFiles: ['./node_modules/react-native-gesture-handler/jestSetup.js'],\n transformIgnorePatterns: [\n 'node_modules/(?!(jest-)?@?react-native|@react-native-community|@react-navigation)',\n\n // For pnpm you need to use inlcude `(?!(?:.pnpm/)?` part like this:\n // 'node_modules/(?!(?:.pnpm/)?((jest-)?@?react-native|@react-native-community|@react-navigation))',\n ],\n};\n")),(0,i.kt)("p",null,"Notice the 2 entries that don't come with the default React Native project:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"setupFiles")," \u2013 an array of files that Jest is going to execute before running your tests. In this case, we run ",(0,i.kt)("inlineCode",{parentName:"li"},"react-native-gesture-handler/jestSetup.js")," which sets up necessary mocks for ",(0,i.kt)("inlineCode",{parentName:"li"},"react-native-gesture-handler")," native module"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"transformIgnorePatterns")," \u2013 an array of paths that Jest ignores when transforming code. In this case, the negative lookahead regular expression is used, to tell Jest to transform (with Babel) every package inside ",(0,i.kt)("inlineCode",{parentName:"li"},"node_modules/")," that starts with ",(0,i.kt)("inlineCode",{parentName:"li"},"react-native"),", ",(0,i.kt)("inlineCode",{parentName:"li"},"@react-native-community")," or ",(0,i.kt)("inlineCode",{parentName:"li"},"@react-navigation")," (added by us, the rest is in ",(0,i.kt)("inlineCode",{parentName:"li"},"react-native")," preset by default, so you don't have to worry about it).")),(0,i.kt)("h3",{id:"example-tests"},"Example tests"),(0,i.kt)("p",null,"For this example, we are going to test out two things. The first thing is that the page is laid out as expected. The second, and most important, is that the page will transition to the detail screen when any item is tapped on."),(0,i.kt)("p",null,"Let's add a ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/callstack/react-native-testing-library/blob/main/examples/reactnavigation/src/__tests__/AppNavigator.js"},(0,i.kt)("inlineCode",{parentName:"a"},"AppNavigator.test.js"))," file in ",(0,i.kt)("inlineCode",{parentName:"p"},"src/__tests__")," directory:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-jsx"},"import * as React from 'react';\nimport { NavigationContainer } from '@react-navigation/native';\nimport { render, screen, fireEvent } from '@testing-library/react-native';\n\nimport AppNavigator from '../AppNavigator';\n\n// Silence the warning https://github.com/facebook/react-native/issues/11094#issuecomment-263240420\n// Use with React Native <= 0.63\njest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper');\n\n// Use this instead with React Native >= 0.64\n// jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper');\n\ndescribe('Testing react navigation', () => {\n test('page contains the header and 10 items', async () => {\n const component = (\n \n \n \n );\n\n render(component);\n\n const header = await screen.findByText('List of numbers from 1 to 20');\n const items = await screen.findAllByText(/Item number/);\n\n expect(header).toBeOnTheScreen();\n expect(items.length).toBe(10);\n });\n\n test('clicking on one item takes you to the details screen', async () => {\n const component = (\n \n \n \n );\n\n render(component);\n const toClick = await screen.findByText('Item number 5');\n\n fireEvent(toClick, 'press');\n const newHeader = await screen.findByText('Showing details for 5');\n const newBody = await screen.findByText('the number you have chosen is 5');\n\n expect(newHeader).toBeOnTheScreen();\n expect(newBody).toBeOnTheScreen();\n });\n});\n")),(0,i.kt)("h2",{id:"drawer-navigator"},"Drawer Navigator"),(0,i.kt)("p",null,"Testing the Drawer Navigation requires an additional setup step for mocking the Reanimated library."),(0,i.kt)("h3",{id:"setting-up-1"},"Setting up"),(0,i.kt)("p",null,"Install the packages required for React Navigation. For this example, we will use a ",(0,i.kt)("a",{parentName:"p",href:"https://reactnavigation.org/docs/drawer-navigator/"},"drawer navigator")," to transition between a home screen and an additional screen."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"$ yarn add @react-native-community/masked-view @react-navigation/native @react-navigation/drawer react-native-gesture-handler react-native-reanimated react-native-safe-area-context react-native-screens\n")),(0,i.kt)("p",null,"Create a ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/callstack/react-native-testing-library/blob/main/examples/reactnavigation/src/DrawerAppNavigator.js"},(0,i.kt)("inlineCode",{parentName:"a"},"./DrawerAppNavigator.js"))," component which will list the navigation stack:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-jsx"},"import 'react-native-gesture-handler';\nimport React from 'react';\nimport { createDrawerNavigator } from '@react-navigation/drawer';\n\nconst { Screen, Navigator } = createDrawerNavigator();\n\nexport default function Navigation() {\n return (\n \n \n \n \n );\n}\n")),(0,i.kt)("p",null,"Create your two screens which we will transition to and from:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-jsx"},"function HomeScreen({ navigation }) {\n return (\n \n Welcome!\n

Returns a ReactTestInstance with matching accessibilityHint prop.

import { render, screen } from '@testing-library/react-native';

render(<MyComponent />);
const element = screen.getByHintText('Plays a song');

ByA11yState, ByAccessibilityState (deprecated)​

caution

This query has been marked deprecated, as is typically too general to give meaningful results. Therefore, it's better to use one of following options:

getByA11yState, getAllByA11yState, queryByA11yState, queryAllByA11yState, findByA11yState, findAllByA11yState getByAccessibilityState, getAllByAccessibilityState, queryByAccessibilityState, queryAllByAccessibilityState, findByAccessibilityState, findAllByAccessibilityState

getByA11yState(
state: {
disabled?: boolean,
selected?: boolean,
checked?: boolean | 'mixed',
busy?: boolean,
expanded?: boolean,
},
options?: {
includeHiddenElements?: boolean;
},
): ReactTestInstance;

Returns a ReactTestInstance with matching accessibilityState prop or ARIA state props: aria-disabled, aria-selected, aria-checked, aria-busy, and aria-expanded.

import { render, screen } from '@testing-library/react-native';

render(<Component />);
const element = screen.getByA11yState({ disabled: true });
note

Default state for: disabled, selected, and busy keys​

Passing false matcher value will match both elements with explicit false state value and without explicit state value.

For instance, getByA11yState({ disabled: false }) will match elements with following props:

  • accessibilityState={{ disabled: false, ... }}
  • no disabled key under accessibilityState prop, e.g. accessibilityState={{}}
  • no accessibilityState prop at all

Default state for: checked and expanded keys​

Passing false matcher value will only match elements with explicit false state value.

For instance, getByA11yState({ checked: false }) will only match elements with:

  • accessibilityState={{ checked: false, ... }}

but will not match elements with following props:

  • no checked key under accessibilityState prop, e.g. accessibilityState={{}}
  • no accessibilityState prop at all

The difference in handling default values is made to reflect observed accessibility behaviour on iOS and Android platforms.

ByA11yValue, ByAccessibilityValue (deprecated)​

caution

This query has been marked deprecated, as is typically too general to give meaningful results. Therefore, it's better to use one of following options:

getByA11yValue, getAllByA11yValue, queryByA11yValue, queryAllByA11yValue, findByA11yValue, findAllByA11yValue -getByAccessibilityValue, getAllByAccessibilityValue, queryByAccessibilityValue, queryAllByAccessibilityValue, findByAccessibilityValue, findAllByAccessibilityValue

getByA11yValue(
value: {
min?: number;
max?: number;
now?: number;
text?: TextMatch;
},
options?: {
includeHiddenElements?: boolean;
},
): ReactTestInstance;

Returns a host element with matching accessibility value based on aria-valuemin, aria-valuemax, aria-valuenow, aria-valuetext & accessibilityValue props. Only value entires provided to the query will be used to match elements. Element might have additional accessibility value entries and still be matched.

When querying by text entry a string or regex might be used.

import { render, screen } from '@testing-library/react-native';

render(
<View accessibilityValue={{ min: 0, max: 100, now: 25, text: '25%' }} />
);
const element = screen.getByA11yValue({ now: 25 });
const element2 = screen.getByA11yValue({ text: /25/ });

Common options​

Usually query first argument can be a string or a regex. All queries take at least the hidden option as an optionnal second argument and some queries accept more options which change string matching behaviour. See TextMatch for more info.

includeHiddenElements option​

All queries have the includeHiddenElements option which affects whether elements hidden from accessibility are matched by the query. By default queries will not match hidden elements, because the users of the app would not be able to see such elements.

You can configure the default value with the configure function.

This option is also available as hidden alias for compatibility with React Testing Library.

Examples

render(<Text style={{ display: 'none' }}>Hidden from accessibility</Text>);

// Exclude hidden elements
expect(
screen.queryByText('Hidden from accessibility', {
includeHiddenElements: false,
})
).not.toBeOnTheScreen();

// Include hidden elements
expect(screen.getByText('Hidden from accessibility')).toBeOnTheScreen();
expect(
screen.getByText('Hidden from accessibility', { includeHiddenElements: true })
).toBeOnTheScreen();

TextMatch type​

type TextMatch = string | RegExp;

Most of the query APIs take a TextMatch as an argument, which means the argument can be either a string or regex.

Examples​

Given the following render:

render(<Text>Hello World</Text>);

Will find a match:

// Matching a string:
screen.getByText('Hello World'); // full string match
screen.getByText('llo Worl', { exact: false }); // substring match
screen.getByText('hello world', { exact: false }); // ignore case-sensitivity

// Matching a regex:
screen.getByText(/World/); // substring match
screen.getByText(/world/i); // substring match, ignore case
screen.getByText(/^hello world$/i); // full string match, ignore case-sensitivity
screen.getByText(/Hello W?oRlD/i); // advanced regex

Will NOT find a match

// substring does not match
screen.getByText('llo Worl');
// full string does not match
screen.getByText('Goodbye World');

// case-sensitive regex with different case
screen.getByText(/hello world/);

Options​

Precision​

type TextMatchOptions = {
exact?: boolean;
normalizer?: (text: string) => string;
};

Queries that take a TextMatch also accept an object as the second argument that can contain options that affect the precision of string matching:

  • exact: Defaults to true; matches full strings, case-sensitive. When false, matches substrings and is not case-sensitive.
    • exact has no effect on regex argument.
    • In most cases using a regex instead of a string gives you more control over fuzzy matching and should be preferred over { exact: false }.
  • normalizer: An optional function which overrides normalization behavior. See Normalization.

exact option defaults to true but if you want to search for a text slice or make text matching case-insensitive you can override it. That being said we advise you to use regex in more complex scenarios.

Normalization​

Before running any matching logic against text, it is automatically normalized. By default, normalization consists of trimming whitespace from the start and end of text, and collapsing multiple adjacent whitespace characters into a single space.

If you want to prevent that normalization, or provide alternative normalization (e.g. to remove Unicode control characters), you can provide a normalizer function in the options object. This function will be given a string and is expected to return a normalized version of that string.

Specifying a value for normalizer replaces the built-in normalization, but you can call getDefaultNormalizer to obtain a built-in normalizer, either to adjust that normalization or to call it from your own normalizer. -:::

getDefaultNormalizer take options object which allows the selection of behaviour:

  • trim: Defaults to true. Trims leading and trailing whitespace.
  • collapseWhitespace: Defaults to true. Collapses inner whitespace (newlines, tabs repeated spaces) into a single space.
Normalization Examples​

To perform a match against text without trimming:

screen.getByText(node, 'text', {
normalizer: getDefaultNormalizer({ trim: false }),
});

To override normalization to remove some Unicode characters whilst keeping some (but not all) of the built-in normalization behavior:

screen.getByText(node, 'text', {
normalizer: (str) =>
getDefaultNormalizer({ trim: false })(str).replace(/[\u200E-\u200F]*/g, ''),
});

Legacy unit testing helpers​

Use sparingly and responsibly, escape hatches here

render from @testing-library/react-native exposes additional queries that should not be used in component integration testing, but some users (like component library creators) interested in unit testing some components may find helpful.

Queries helpful in unit testing

The interface is the same as for other queries, but we won't provide full names so that they're harder to find by search engines.

UNSAFE_ByType​

UNSAFE_getByType, UNSAFE_getAllByType, UNSAFE_queryByType, UNSAFE_queryAllByType

Returns a ReactTestInstance with matching a React component type.

caution

This query has been marked unsafe, since it requires knowledge about implementation details of the component. Use responsibly.

UNSAFE_ByProps​

UNSAFE_getByProps, UNSAFE_getAllByProps, UNSAFE_queryByProps, UNSAFE_queryAllByProps

Returns a ReactTestInstance with matching props object.

caution

This query has been marked unsafe, since it requires knowledge about implementation details of the component. Use responsibly.

- +getByAccessibilityValue, getAllByAccessibilityValue, queryByAccessibilityValue, queryAllByAccessibilityValue, findByAccessibilityValue, findAllByAccessibilityValue

getByA11yValue(
value: {
min?: number;
max?: number;
now?: number;
text?: TextMatch;
},
options?: {
includeHiddenElements?: boolean;
},
): ReactTestInstance;

Returns a host element with matching accessibility value based on aria-valuemin, aria-valuemax, aria-valuenow, aria-valuetext & accessibilityValue props. Only value entires provided to the query will be used to match elements. Element might have additional accessibility value entries and still be matched.

When querying by text entry a string or regex might be used.

import { render, screen } from '@testing-library/react-native';

render(<View accessibilityValue={{ min: 0, max: 100, now: 25, text: '25%' }} />);
const element = screen.getByA11yValue({ now: 25 });
const element2 = screen.getByA11yValue({ text: /25/ });

Common options​

Usually query first argument can be a string or a regex. All queries take at least the hidden option as an optionnal second argument and some queries accept more options which change string matching behaviour. See TextMatch for more info.

includeHiddenElements option​

All queries have the includeHiddenElements option which affects whether elements hidden from accessibility are matched by the query. By default queries will not match hidden elements, because the users of the app would not be able to see such elements.

You can configure the default value with the configure function.

This option is also available as hidden alias for compatibility with React Testing Library.

Examples

render(<Text style={{ display: 'none' }}>Hidden from accessibility</Text>);

// Exclude hidden elements
expect(
screen.queryByText('Hidden from accessibility', {
includeHiddenElements: false,
})
).not.toBeOnTheScreen();

// Include hidden elements
expect(screen.getByText('Hidden from accessibility')).toBeOnTheScreen();
expect(
screen.getByText('Hidden from accessibility', { includeHiddenElements: true })
).toBeOnTheScreen();

TextMatch type​

type TextMatch = string | RegExp;

Most of the query APIs take a TextMatch as an argument, which means the argument can be either a string or regex.

Examples​

Given the following render:

render(<Text>Hello World</Text>);

Will find a match:

// Matching a string:
screen.getByText('Hello World'); // full string match
screen.getByText('llo Worl', { exact: false }); // substring match
screen.getByText('hello world', { exact: false }); // ignore case-sensitivity

// Matching a regex:
screen.getByText(/World/); // substring match
screen.getByText(/world/i); // substring match, ignore case
screen.getByText(/^hello world$/i); // full string match, ignore case-sensitivity
screen.getByText(/Hello W?oRlD/i); // advanced regex

Will NOT find a match

// substring does not match
screen.getByText('llo Worl');
// full string does not match
screen.getByText('Goodbye World');

// case-sensitive regex with different case
screen.getByText(/hello world/);

Options​

Precision​

type TextMatchOptions = {
exact?: boolean;
normalizer?: (text: string) => string;
};

Queries that take a TextMatch also accept an object as the second argument that can contain options that affect the precision of string matching:

  • exact: Defaults to true; matches full strings, case-sensitive. When false, matches substrings and is not case-sensitive.
    • exact has no effect on regex argument.
    • In most cases using a regex instead of a string gives you more control over fuzzy matching and should be preferred over { exact: false }.
  • normalizer: An optional function which overrides normalization behavior. See Normalization.

exact option defaults to true but if you want to search for a text slice or make text matching case-insensitive you can override it. That being said we advise you to use regex in more complex scenarios.

Normalization​

Before running any matching logic against text, it is automatically normalized. By default, normalization consists of trimming whitespace from the start and end of text, and collapsing multiple adjacent whitespace characters into a single space.

If you want to prevent that normalization, or provide alternative normalization (e.g. to remove Unicode control characters), you can provide a normalizer function in the options object. This function will be given a string and is expected to return a normalized version of that string.

Specifying a value for normalizer replaces the built-in normalization, but you can call getDefaultNormalizer to obtain a built-in normalizer, either to adjust that normalization or to call it from your own normalizer. +:::

getDefaultNormalizer take options object which allows the selection of behaviour:

  • trim: Defaults to true. Trims leading and trailing whitespace.
  • collapseWhitespace: Defaults to true. Collapses inner whitespace (newlines, tabs repeated spaces) into a single space.
Normalization Examples​

To perform a match against text without trimming:

screen.getByText(node, 'text', {
normalizer: getDefaultNormalizer({ trim: false }),
});

To override normalization to remove some Unicode characters whilst keeping some (but not all) of the built-in normalization behavior:

screen.getByText(node, 'text', {
normalizer: (str) => getDefaultNormalizer({ trim: false })(str).replace(/[\u200E-\u200F]*/g, ''),
});

Legacy unit testing helpers​

Use sparingly and responsibly, escape hatches here

render from @testing-library/react-native exposes additional queries that should not be used in component integration testing, but some users (like component library creators) interested in unit testing some components may find helpful.

Queries helpful in unit testing

The interface is the same as for other queries, but we won't provide full names so that they're harder to find by search engines.

UNSAFE_ByType​

UNSAFE_getByType, UNSAFE_getAllByType, UNSAFE_queryByType, UNSAFE_queryAllByType

Returns a ReactTestInstance with matching a React component type.

caution

This query has been marked unsafe, since it requires knowledge about implementation details of the component. Use responsibly.

UNSAFE_ByProps​

UNSAFE_getByProps, UNSAFE_getAllByProps, UNSAFE_queryByProps, UNSAFE_queryAllByProps

Returns a ReactTestInstance with matching props object.

caution

This query has been marked unsafe, since it requires knowledge about implementation details of the component. Use responsibly.

+ \ No newline at end of file diff --git a/docs/api.html b/docs/api.html index 7bfe0e30..3fb56dd0 100644 --- a/docs/api.html +++ b/docs/api.html @@ -4,14 +4,14 @@ API | React Native Testing Library - +

API

render API​

Defined as:

function render(
component: React.Element<any>,
options?: RenderOptions
): RenderResult {}

Deeply renders given React element and returns helpers to query the output components structure.

import { render } from '@testing-library/react-native';
import { QuestionsBoard } from '../QuestionsBoard';

test('should verify two questions', () => {
render(<QuestionsBoard {...props} />);
const allQuestions = screen.queryAllByRole('header');

expect(allQuestions).toHaveLength(2);
});

When using React context providers, like Redux Provider, you'll likely want to wrap rendered component with them. In such cases it's convenient to create your custom render method. Follow this great guide on how to set this up.

The render method returns a RenderResult object having properties described below.

info

Latest render result is kept in screen variable that can be imported from @testing-library/react-native package.

Using screen instead of destructuring render result is recommended approach. See this article from Kent C. Dodds for more details.

Options​

The behavior of render method can be customized by passing various options as a second argument of RenderOptions type:

wrapper option​

wrapper?: React.ComponentType<any>,

This options allows you to wrap tested component, passed as the first option to the render() function, in additional wrapper component. This is most useful for creating reusable custom render functions for common React Context providers.

createNodeMock option​

createNodeMock?: (element: React.Element<any>) => any,

This options allows you to pass createNodeMock option to ReactTestRenderer.create() method in order to allow for custom mock refs. You can learn more about this options from React Test Renderer documentation.

unstable_validateStringsRenderedWithinText option​

unstable_validateStringsRenderedWithinText?: boolean;
note

This options is experimental, in some cases it might not work as intended, and its behavior might change without observing SemVer requirements for breaking changes.

This experimental option allows you to replicate React Native behavior of throwing Invariant Violation: Text strings must be rendered within a <Text> component error when you try to render string value under components different than <Text>, e.g. under <View>.

This check is not enforced by React Test Renderer and hence by default React Native Testing Library also does not check this. That might result in runtime errors when running your code on a device, while the code works without errors in tests.

...queries​

The most important feature of render is providing a set of helpful queries that allow you to find certain elements in the view hierarchy.

See Queries for a complete list.

Example​

import { render } from '@testing-library/react-native';

const { getByText, queryByA11yState } = render(<Component />);

update​

Also available under rerender alias

update(element: React.Element<any>): void
rerender(element: React.Element<any>): void

Re-render the in-memory tree with a new root element. This simulates a React update at the root. If the new element has the same type and key as the previous element, the tree will be updated; otherwise, it will re-mount a new tree. This is useful when testing for componentDidUpdate behavior, by passing updated props to the component.

Example code

unmount​

unmount(): void

Unmount the in-memory tree, triggering the appropriate lifecycle events.

note

Usually you should not need to call unmount as it is done automatically if your test runner supports afterEach hook (like Jest, mocha, Jasmine).

debug​

interface DebugOptions {
message?: string;
mapProps?: MapPropsFunction;
}

debug(options?: DebugOptions | string): void

Pretty prints deeply rendered component passed to render.

message option​

You can provide a message that will be printed on top.

render(<Component />);
screen.debug({ message: 'optional message' });

logs optional message and colored JSX:

optional message

<View
onPress={[Function bound fn]}
>
<Text>Press me</Text>
</View>

mapProps option​

You can use the mapProps option to transform the props that will be printed :

render(<View style={{ backgroundColor: 'red' }} />);
debug({ mapProps: ({ style, ...props }) => ({ props }) });

This will log the rendered JSX without the style props.

The children prop cannot be filtered out so the following will print all rendered components with all props but children filtered out.

debug({ mapProps: (props) => ({}) });

This option can be used to target specific props when debugging a query (for instance keeping only children prop when debugging a getByText query).

You can also transform prop values so that they are more readable (e.g. flatten styles).

import { StyleSheet } from 'react-native';

debug({ mapProps : {({ style, ...props })} => ({ style : StyleSheet.flatten(style), ...props }) });

Or remove props that have little value when debugging tests, e.g. path prop for svgs

debug({ mapProps: ({ path, ...props }) => ({ ...props }) });

debug.shallow​

Pretty prints shallowly rendered component passed to render with optional message on top.

toJSON​

toJSON(): ReactTestRendererJSON | null

Get the rendered component JSON representation, e.g. for snapshot testing.

root​

root: ReactTestInstance;

Returns the rendered root host element.

This API is primarily useful in component tests, as it allows you to access root host view without using *ByTestId queries or similar methods.

UNSAFE_root​

UNSAFE_root: ReactTestInstance;

Returns the rendered composite root element.

caution

This API typically will return a composite view which goes against recommended testing practices. This API is primarily available for legacy test suites that rely on such testing.

note

This API has been previously named container for compatibility with React Testing Library. However, despite the same name, the actual behavior has been signficantly different, hence the name change to UNSAFE_root.

screen API​

let screen: RenderResult;

Hold the value of latest render call for easier access to query and other functions returned by render.

Its value is automatically cleared after each test by calling cleanup. If no render call has been made in a given test then it holds a special object that implements RenderResult but throws a helpful error on each property and method access.

This can also be used to build test utils that would normally require to be in render scope, either in a test file or globally for your project. For instance:

// Prints the rendered components omitting all props except children.
const debugText = () => screen.debug({ mapProps: (props) => ({}) });

fireEvent API​

function fireEvent(
element: ReactTestInstance,
eventName: string,
...data: Array<any>
): void {}
note

For common events like press or type it's recommended to use User Event API as it offers -more realistic event simulation by emitting a sequence of events with proper event objects that mimic React Native runtime behavior.

Use Fire Event for cases not supported by User Event and for triggering event handlers on composite components.

fireEvent API allows you to trigger all kind of event handlers on both host and composite components. It will try to invoke a single event handler traversing the component tree bottom-up from passed element and trying to find enabled event handler named onXxx when xxx is the name of the event passed.

Unlike User Event, this API does not automatically pass event object to event handler, this is responsibility of the user to construct such object.

import { render, screen, fireEvent } from '@testing-library/react-native';

test('fire changeText event', () => {
const onEventMock = jest.fn();
render(
// MyComponent renders TextInput which has a placeholder 'Enter details'
// and with `onChangeText` bound to handleChangeText
<MyComponent handleChangeText={onEventMock} />
);

fireEvent(screen.getByPlaceholderText('change'), 'onChangeText', 'ab');
expect(onEventMock).toHaveBeenCalledWith('ab');
});
note

Please note that from version 7.0 fireEvent performs checks that should prevent events firing on disabled elements.

An example using fireEvent with native events that aren't already aliased by the fireEvent api.

import { TextInput, View } from 'react-native';
import { fireEvent, render } from '@testing-library/react-native';

const onBlurMock = jest.fn();

render(
<View>
<TextInput placeholder="my placeholder" onBlur={onBlurMock} />
</View>
);

// you can omit the `on` prefix
fireEvent(screen.getByPlaceholderText('my placeholder'), 'blur');

FireEvent exposes convenience methods for common events like: press, changeText, scroll.

fireEvent.press​

fireEvent.press: (element: ReactTestInstance, ...data: Array<any>) => void
note

It is recommended to use the User Event press() helper instead as it offers more realistic simulation of press interaction, including pressable support.

Invokes press event handler on the element or parent element in the tree.

import { View, Text, TouchableOpacity } from 'react-native';
import { render, screen, fireEvent } from '@testing-library/react-native';

const onPressMock = jest.fn();
const eventData = {
nativeEvent: {
pageX: 20,
pageY: 30,
},
};

render(
<View>
<TouchableOpacity onPress={onPressMock}>
<Text>Press me</Text>
</TouchableOpacity>
</View>
);

fireEvent.press(screen.getByText('Press me'), eventData);
expect(onPressMock).toHaveBeenCalledWith(eventData);

fireEvent.changeText​

fireEvent.changeText: (element: ReactTestInstance, ...data: Array<any>) => void
note

It is recommended to use the User Event type() helper instead as it offers more realistic simulation of text change interaction, including key-by-key typing, element focus, and other editing events.

Invokes changeText event handler on the element or parent element in the tree.

import { View, TextInput } from 'react-native';
import { render, screen, fireEvent } from '@testing-library/react-native';

const onChangeTextMock = jest.fn();
const CHANGE_TEXT = 'content';

render(
<View>
<TextInput placeholder="Enter data" onChangeText={onChangeTextMock} />
</View>
);

fireEvent.changeText(screen.getByPlaceholderText('Enter data'), CHANGE_TEXT);

fireEvent.scroll​

fireEvent.scroll: (element: ReactTestInstance, ...data: Array<any>) => void

Invokes scroll event handler on the element or parent element in the tree.

On a ScrollView​

import { ScrollView, Text } from 'react-native';
import { render, screen, fireEvent } from '@testing-library/react-native';

const onScrollMock = jest.fn();
const eventData = {
nativeEvent: {
contentOffset: {
y: 200,
},
},
};

render(
<ScrollView onScroll={onScrollMock}>
<Text>XD</Text>
</ScrollView>
);

fireEvent.scroll(screen.getByText('scroll-view'), eventData);

Helper functions​

waitFor​

Defined as:

function waitFor<T>(
expectation: () => T,
{ timeout: number = 1000, interval: number = 50 }
): Promise<T> {}

Waits for a period of time for the expectation callback to pass. waitFor may run the callback a number of times until timeout is reached, as specified by the timeout and interval options. The callback must throw an error when the expectation is not met. Returning any value, including a falsy one, will be treated as meeting the expectation, and the callback result will be returned to the caller of waitFor function.

await waitFor(() => expect(mockFunction).toHaveBeenCalledWith()))

waitFor function will be executing expectation callback every interval (default: every 50 ms) until timeout (default: 1000 ms) is reached. The repeated execution of callback is stopped as soon as it does not throw an error, in such case the value returned by the callback is returned to waitFor caller. Otherwise, when it reaches the timeout, the final error thrown by expectation will be re-thrown by waitFor to the calling code.

// ❌ `waitFor` will return immediately because callback does not throw
await waitFor(() => false);

waitFor is an async function so you need to await the result to pause test execution.

// ❌ missing `await`: `waitFor` will just return Promise that will be rejected when the timeout is reached
waitFor(() => expect(1).toBe(2));
note

You can enforce awaiting waitFor by using the await-async-utils rule from eslint-plugin-testing-library.

Since waitFor is likely to run expectation callback multiple times, it is highly recommended for it not to perform any side effects in waitFor.

await waitFor(() => {
// ❌ button will be pressed on each waitFor iteration
fireEvent.press(screen.getByText('press me'));
expect(mockOnPress).toHaveBeenCalled();
});
note

Avoiding side effects in expectation callback can be partially enforced with the no-wait-for-side-effects rule.

It is also recommended to have a single assertion per each waitFor for more consistency and faster failing tests. If you want to make several assertions, then they should be in seperate waitFor calls. In many cases you won't actually need to wrap the second assertion in waitFor since the first one will do the waiting required for asynchronous change to happen.

Using a React Native version < 0.71 with Jest fake timers​

caution

When using a version of React Native < 0.71 and modern fake timers (the default for Jest >= 27), waitFor won't work (it will always timeout even if expectation() doesn't throw) unless you use the custom @testing-library/react-native preset.

waitFor checks whether Jest fake timers are enabled and adapts its behavior in such case. The following snippet is a simplified version of how it behaves when fake timers are enabled:

let fakeTimeRemaining = timeout;
let lastError;

while (fakeTimeRemaining > 0) {
fakeTimeRemaining = fakeTimeRemaining - interval;
jest.advanceTimersByTime(interval);
try {
// resolve
return expectation();
} catch (error) {
lastError = error;
}
}

// reject
throw lastError;

In the following example we test that a function is called after 10 seconds using fake timers. Since we're using fake timers, the test won't depend on real time passing and thus be much faster and more reliable. Also we don't have to advance fake timers through Jest fake timers API because waitFor already does this for us.

// in component
setTimeout(() => {
someFunction();
}, 10000);

// in test
jest.useFakeTimers();

await waitFor(() => {
expect(someFunction).toHaveBeenCalledWith();
}, 10000);
info

In order to properly use waitFor you need at least React >=16.9.0 (featuring async act) or React Native >=0.61 (which comes with React >=16.9.0).

note

If you receive warnings related to act() function consult our Undestanding Act function document.

waitForElementToBeRemoved​

Defined as:

function waitForElementToBeRemoved<T>(
expectation: () => T,
{ timeout: number = 4500, interval: number = 50 }
): Promise<T> {}

Waits for non-deterministic periods of time until queried element is removed or times out. waitForElementToBeRemoved periodically calls expectation every interval milliseconds to determine whether the element has been removed or not.

import {
render,
screen,
waitForElementToBeRemoved,
} from '@testing-library/react-native';

test('waiting for an Banana to be removed', async () => {
render(<Banana />);

await waitForElementToBeRemoved(() => screen.getByText('Banana ready'));
});

This method expects that the element is initially present in the render tree and then is removed from it. If the element is not present when you call this method it throws an error.

You can use any of getBy, getAllBy, queryBy and queryAllBy queries for expectation parameter.

info

In order to properly use waitForElementToBeRemoved you need at least React >=16.9.0 (featuring async act) or React Native >=0.61 (which comes with React >=16.9.0).

note

If you receive warnings related to act() function consult our Undestanding Act function document.

within, getQueriesForElement​

Defined as:

function within(element: ReactTestInstance): Queries {}

function getQueriesForElement(element: ReactTestInstance): Queries {}

within (also available as getQueriesForElement alias) performs queries scoped to given element.

note

Please note that additional render specific operations like update, unmount, debug, toJSON are not included.

const detailsScreen = within(screen.getByA11yHint('Details Screen'));
expect(detailsScreen.getByText('Some Text')).toBeOnTheScreen();
expect(detailsScreen.getByDisplayValue('Some Value')).toBeOnTheScreen();
expect(detailsScreen.queryByLabelText('Some Label')).toBeOnTheScreen();
await expect(
detailsScreen.findByA11yHint('Some Label')
).resolves.toBeOnTheScreen();

Use cases for scoped queries include:

  • queries scoped to a single item inside a FlatList containing many items
  • queries scoped to a single screen in tests involving screen transitions (e.g. with react-navigation)

act​

Useful function to help testing components that use hooks API. By default any render, update, fireEvent, and waitFor calls are wrapped by this function, so there is no need to wrap it manually. This method is re-exported from react-test-renderer.

Consult our Undestanding Act function document for more understanding of its intricacies.

cleanup​

const cleanup: () => void;

Unmounts React trees that were mounted with render and clears screen variable that holds latest render output.

info

Please note that this is done automatically if the testing framework you're using supports the afterEach global (like mocha, Jest, and Jasmine). If not, you will need to do manual cleanups after each test.

For example, if you're using the jest testing framework, then you would need to use the afterEach hook like so:

import { cleanup, render } from '@testing-library/react-native/pure';
import { View } from 'react-native';

afterEach(cleanup);

it('renders a view', () => {
render(<View />);
// ...
});

The afterEach(cleanup) call also works in describe blocks:

describe('when logged in', () => {
afterEach(cleanup);

it('renders the user', () => {
render(<SiteHeader />);
// ...
});
});

Failing to call cleanup when you've called render could result in a memory leak and tests which are not "idempotent" (which can lead to difficult to debug errors in your tests).

renderHook API​

Defined as:

function renderHook<Result, Props>(
callback: (props?: Props) => Result,
options?: RenderHookOptions<Props>
): RenderHookResult<Result, Props>;

Renders a test component that will call the provided callback, including any hooks it calls, every time it renders. Returns RenderHookResult object, which you can interact with.

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

it('should increment count', () => {
const { result } = renderHook(() => useCount());

expect(result.current.count).toBe(0);
act(() => {
// Note that you should wrap the calls to functions your hook returns with `act` if they trigger an update of your hook's state to ensure pending useEffects are run before your next assertion.
result.current.increment();
});
expect(result.current.count).toBe(1);
});
// useCount.js
export const useCount = () => {
const [count, setCount] = useState(0);
const increment = () => setCount((previousCount) => previousCount + 1);

return { count, increment };
};

The renderHook function accepts the following arguments:

Callback is a function that is called each render of the test component. This function should call one or more hooks for testing.

The props passed into the callback will be the initialProps provided in the options to renderHook, unless new props are provided by a subsequent rerender call.

options​

A RenderHookOptions<Props> object to modify the execution of the callback function, containing the following properties:

initialProps​

The initial values to pass as props to the callback function of renderHook. The Props type is determined by the type passed to or inferred by the renderHook call.

wrapper​

A React component to wrap the test component in when rendering. This is usually used to add context providers from React.createContext for the hook to access with useContext.

RenderHookResult​

interface RenderHookResult<Result, Props> {
result: { current: Result };
rerender: (props: Props) => void;
unmount: () => void;
}

The renderHook function returns an object that has the following properties:

result​

The current value of the result will reflect the latest of whatever is returned from the callback passed to renderHook. The Result type is determined by the type passed to or inferred by the renderHook call.

rerender​

A function to rerender the test component, causing any hooks to be recalculated. If newProps are passed, they will replace the callback function's initialProps for subsequent rerenders. The Props type is determined by the type passed to or inferred by the renderHook call.

unmount​

A function to unmount the test component. This is commonly used to trigger cleanup effects for useEffect hooks.

Examples​

Here we present some extra examples of using renderHook API.

With initialProps​

const useCount = (initialCount: number) => {
const [count, setCount] = useState(initialCount);
const increment = () => setCount((previousCount) => previousCount + 1);

useEffect(() => {
setCount(initialCount);
}, [initialCount]);

return { count, increment };
};

it('should increment count', () => {
const { result, rerender } = renderHook(
(initialCount: number) => useCount(initialCount),
{ initialProps: 1 }
);

expect(result.current.count).toBe(1);

act(() => {
result.current.increment();
});

expect(result.current.count).toBe(2);
rerender(5);
expect(result.current.count).toBe(5);
});

With wrapper​

it('should use context value', () => {
function Wrapper({ children }: { children: ReactNode }) {
return <Context.Provider value="provided">{children}</Context.Provider>;
}

const { result } = renderHook(() => useHook(), { wrapper: Wrapper });
// ...
});

Configuration​

configure​

type Config = {
asyncUtilTimeout: number;
defaultHidden: boolean;
defaultDebugOptions: Partial<DebugOptions>;
};

function configure(options: Partial<Config>) {}

asyncUtilTimeout option​

Default timeout, in ms, for async helper functions (waitFor, waitForElementToBeRemoved) and findBy* queries. Defaults to 1000 ms.

defaultIncludeHiddenElements option​

Default value for includeHiddenElements query option for all queries. The default value is set to false, so all queries will not match elements hidden from accessibility. This is because the users of the app would not be able to see such elements.

This option is also available as defaultHidden alias for compatibility with React Testing Library.

defaultDebugOptions option​

Default debug options to be used when calling debug(). These default options will be overridden by the ones you specify directly when calling debug().

resetToDefaults()​

function resetToDefaults() {}

Environment variables​

RNTL_SKIP_AUTO_CLEANUP​

Set to true to disable automatic cleanup() after each test. It works the same as importing react-native-testing-library/dont-cleanup-after-each or using react-native-testing-library/pure.

$ RNTL_SKIP_AUTO_CLEANUP=true jest

RNTL_SKIP_AUTO_DETECT_FAKE_TIMERS​

Set to true to disable auto-detection of fake timers. This might be useful in rare cases when you want to use non-Jest fake timers. See issue #886 for more details.

$ RNTL_SKIP_AUTO_DETECT_FAKE_TIMERS=true jest

Accessibility​

isHiddenFromAccessibility​

function isHiddenFromAccessibility(
element: ReactTestInstance | null
): boolean {}

Also available as isInaccessible() alias for React Testing Library compatibility.

Checks if given element is hidden from assistive technology, e.g. screen readers.

note

Like isInaccessible function from DOM Testing Library this function considers both accessibility elements and presentational elements (regular Views) to be accessible, unless they are hidden in terms of host platform.

This covers only part of ARIA notion of Accessiblity Tree, as ARIA excludes both hidden and presentational elements from the Accessibility Tree.

For the scope of this function, element is inaccessible when it, or any of its ancestors, meets any of the following conditions:

Specifying accessible={false}, accessiblityRole="none", or importantForAccessibility="no" props does not cause the element to become inaccessible.

- +more realistic event simulation by emitting a sequence of events with proper event objects that mimic React Native runtime behavior.

Use Fire Event for cases not supported by User Event and for triggering event handlers on composite components.

fireEvent API allows you to trigger all kind of event handlers on both host and composite components. It will try to invoke a single event handler traversing the component tree bottom-up from passed element and trying to find enabled event handler named onXxx when xxx is the name of the event passed.

Unlike User Event, this API does not automatically pass event object to event handler, this is responsibility of the user to construct such object.

import { render, screen, fireEvent } from '@testing-library/react-native';

test('fire changeText event', () => {
const onEventMock = jest.fn();
render(
// MyComponent renders TextInput which has a placeholder 'Enter details'
// and with `onChangeText` bound to handleChangeText
<MyComponent handleChangeText={onEventMock} />
);

fireEvent(screen.getByPlaceholderText('change'), 'onChangeText', 'ab');
expect(onEventMock).toHaveBeenCalledWith('ab');
});
note

Please note that from version 7.0 fireEvent performs checks that should prevent events firing on disabled elements.

An example using fireEvent with native events that aren't already aliased by the fireEvent api.

import { TextInput, View } from 'react-native';
import { fireEvent, render } from '@testing-library/react-native';

const onBlurMock = jest.fn();

render(
<View>
<TextInput placeholder="my placeholder" onBlur={onBlurMock} />
</View>
);

// you can omit the `on` prefix
fireEvent(screen.getByPlaceholderText('my placeholder'), 'blur');

FireEvent exposes convenience methods for common events like: press, changeText, scroll.

fireEvent.press​

fireEvent.press: (element: ReactTestInstance, ...data: Array<any>) => void
note

It is recommended to use the User Event press() helper instead as it offers more realistic simulation of press interaction, including pressable support.

Invokes press event handler on the element or parent element in the tree.

import { View, Text, TouchableOpacity } from 'react-native';
import { render, screen, fireEvent } from '@testing-library/react-native';

const onPressMock = jest.fn();
const eventData = {
nativeEvent: {
pageX: 20,
pageY: 30,
},
};

render(
<View>
<TouchableOpacity onPress={onPressMock}>
<Text>Press me</Text>
</TouchableOpacity>
</View>
);

fireEvent.press(screen.getByText('Press me'), eventData);
expect(onPressMock).toHaveBeenCalledWith(eventData);

fireEvent.changeText​

fireEvent.changeText: (element: ReactTestInstance, ...data: Array<any>) => void
note

It is recommended to use the User Event type() helper instead as it offers more realistic simulation of text change interaction, including key-by-key typing, element focus, and other editing events.

Invokes changeText event handler on the element or parent element in the tree.

import { View, TextInput } from 'react-native';
import { render, screen, fireEvent } from '@testing-library/react-native';

const onChangeTextMock = jest.fn();
const CHANGE_TEXT = 'content';

render(
<View>
<TextInput placeholder="Enter data" onChangeText={onChangeTextMock} />
</View>
);

fireEvent.changeText(screen.getByPlaceholderText('Enter data'), CHANGE_TEXT);

fireEvent.scroll​

fireEvent.scroll: (element: ReactTestInstance, ...data: Array<any>) => void

Invokes scroll event handler on the element or parent element in the tree.

On a ScrollView​

import { ScrollView, Text } from 'react-native';
import { render, screen, fireEvent } from '@testing-library/react-native';

const onScrollMock = jest.fn();
const eventData = {
nativeEvent: {
contentOffset: {
y: 200,
},
},
};

render(
<ScrollView onScroll={onScrollMock}>
<Text>XD</Text>
</ScrollView>
);

fireEvent.scroll(screen.getByText('scroll-view'), eventData);

Helper functions​

waitFor​

Defined as:

function waitFor<T>(
expectation: () => T,
{ timeout: number = 1000, interval: number = 50 }
): Promise<T> {}

Waits for a period of time for the expectation callback to pass. waitFor may run the callback a number of times until timeout is reached, as specified by the timeout and interval options. The callback must throw an error when the expectation is not met. Returning any value, including a falsy one, will be treated as meeting the expectation, and the callback result will be returned to the caller of waitFor function.

await waitFor(() => expect(mockFunction).toHaveBeenCalledWith()))

waitFor function will be executing expectation callback every interval (default: every 50 ms) until timeout (default: 1000 ms) is reached. The repeated execution of callback is stopped as soon as it does not throw an error, in such case the value returned by the callback is returned to waitFor caller. Otherwise, when it reaches the timeout, the final error thrown by expectation will be re-thrown by waitFor to the calling code.

// ❌ `waitFor` will return immediately because callback does not throw
await waitFor(() => false);

waitFor is an async function so you need to await the result to pause test execution.

// ❌ missing `await`: `waitFor` will just return Promise that will be rejected when the timeout is reached
waitFor(() => expect(1).toBe(2));
note

You can enforce awaiting waitFor by using the await-async-utils rule from eslint-plugin-testing-library.

Since waitFor is likely to run expectation callback multiple times, it is highly recommended for it not to perform any side effects in waitFor.

await waitFor(() => {
// ❌ button will be pressed on each waitFor iteration
fireEvent.press(screen.getByText('press me'));
expect(mockOnPress).toHaveBeenCalled();
});
note

Avoiding side effects in expectation callback can be partially enforced with the no-wait-for-side-effects rule.

It is also recommended to have a single assertion per each waitFor for more consistency and faster failing tests. If you want to make several assertions, then they should be in seperate waitFor calls. In many cases you won't actually need to wrap the second assertion in waitFor since the first one will do the waiting required for asynchronous change to happen.

Using a React Native version < 0.71 with Jest fake timers​

caution

When using a version of React Native < 0.71 and modern fake timers (the default for Jest >= 27), waitFor won't work (it will always timeout even if expectation() doesn't throw) unless you use the custom @testing-library/react-native preset.

waitFor checks whether Jest fake timers are enabled and adapts its behavior in such case. The following snippet is a simplified version of how it behaves when fake timers are enabled:

let fakeTimeRemaining = timeout;
let lastError;

while (fakeTimeRemaining > 0) {
fakeTimeRemaining = fakeTimeRemaining - interval;
jest.advanceTimersByTime(interval);
try {
// resolve
return expectation();
} catch (error) {
lastError = error;
}
}

// reject
throw lastError;

In the following example we test that a function is called after 10 seconds using fake timers. Since we're using fake timers, the test won't depend on real time passing and thus be much faster and more reliable. Also we don't have to advance fake timers through Jest fake timers API because waitFor already does this for us.

// in component
setTimeout(() => {
someFunction();
}, 10000);

// in test
jest.useFakeTimers();

await waitFor(() => {
expect(someFunction).toHaveBeenCalledWith();
}, 10000);
info

In order to properly use waitFor you need at least React >=16.9.0 (featuring async act) or React Native >=0.61 (which comes with React >=16.9.0).

note

If you receive warnings related to act() function consult our Undestanding Act function document.

waitForElementToBeRemoved​

Defined as:

function waitForElementToBeRemoved<T>(
expectation: () => T,
{ timeout: number = 4500, interval: number = 50 }
): Promise<T> {}

Waits for non-deterministic periods of time until queried element is removed or times out. waitForElementToBeRemoved periodically calls expectation every interval milliseconds to determine whether the element has been removed or not.

import { render, screen, waitForElementToBeRemoved } from '@testing-library/react-native';

test('waiting for an Banana to be removed', async () => {
render(<Banana />);

await waitForElementToBeRemoved(() => screen.getByText('Banana ready'));
});

This method expects that the element is initially present in the render tree and then is removed from it. If the element is not present when you call this method it throws an error.

You can use any of getBy, getAllBy, queryBy and queryAllBy queries for expectation parameter.

info

In order to properly use waitForElementToBeRemoved you need at least React >=16.9.0 (featuring async act) or React Native >=0.61 (which comes with React >=16.9.0).

note

If you receive warnings related to act() function consult our Undestanding Act function document.

within, getQueriesForElement​

Defined as:

function within(element: ReactTestInstance): Queries {}

function getQueriesForElement(element: ReactTestInstance): Queries {}

within (also available as getQueriesForElement alias) performs queries scoped to given element.

note

Please note that additional render specific operations like update, unmount, debug, toJSON are not included.

const detailsScreen = within(screen.getByA11yHint('Details Screen'));
expect(detailsScreen.getByText('Some Text')).toBeOnTheScreen();
expect(detailsScreen.getByDisplayValue('Some Value')).toBeOnTheScreen();
expect(detailsScreen.queryByLabelText('Some Label')).toBeOnTheScreen();
await expect(detailsScreen.findByA11yHint('Some Label')).resolves.toBeOnTheScreen();

Use cases for scoped queries include:

  • queries scoped to a single item inside a FlatList containing many items
  • queries scoped to a single screen in tests involving screen transitions (e.g. with react-navigation)

act​

Useful function to help testing components that use hooks API. By default any render, update, fireEvent, and waitFor calls are wrapped by this function, so there is no need to wrap it manually. This method is re-exported from react-test-renderer.

Consult our Undestanding Act function document for more understanding of its intricacies.

cleanup​

const cleanup: () => void;

Unmounts React trees that were mounted with render and clears screen variable that holds latest render output.

info

Please note that this is done automatically if the testing framework you're using supports the afterEach global (like mocha, Jest, and Jasmine). If not, you will need to do manual cleanups after each test.

For example, if you're using the jest testing framework, then you would need to use the afterEach hook like so:

import { cleanup, render } from '@testing-library/react-native/pure';
import { View } from 'react-native';

afterEach(cleanup);

it('renders a view', () => {
render(<View />);
// ...
});

The afterEach(cleanup) call also works in describe blocks:

describe('when logged in', () => {
afterEach(cleanup);

it('renders the user', () => {
render(<SiteHeader />);
// ...
});
});

Failing to call cleanup when you've called render could result in a memory leak and tests which are not "idempotent" (which can lead to difficult to debug errors in your tests).

renderHook API​

Defined as:

function renderHook<Result, Props>(
callback: (props?: Props) => Result,
options?: RenderHookOptions<Props>
): RenderHookResult<Result, Props>;

Renders a test component that will call the provided callback, including any hooks it calls, every time it renders. Returns RenderHookResult object, which you can interact with.

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

it('should increment count', () => {
const { result } = renderHook(() => useCount());

expect(result.current.count).toBe(0);
act(() => {
// Note that you should wrap the calls to functions your hook returns with `act` if they trigger an update of your hook's state to ensure pending useEffects are run before your next assertion.
result.current.increment();
});
expect(result.current.count).toBe(1);
});
// useCount.js
export const useCount = () => {
const [count, setCount] = useState(0);
const increment = () => setCount((previousCount) => previousCount + 1);

return { count, increment };
};

The renderHook function accepts the following arguments:

Callback is a function that is called each render of the test component. This function should call one or more hooks for testing.

The props passed into the callback will be the initialProps provided in the options to renderHook, unless new props are provided by a subsequent rerender call.

options​

A RenderHookOptions<Props> object to modify the execution of the callback function, containing the following properties:

initialProps​

The initial values to pass as props to the callback function of renderHook. The Props type is determined by the type passed to or inferred by the renderHook call.

wrapper​

A React component to wrap the test component in when rendering. This is usually used to add context providers from React.createContext for the hook to access with useContext.

RenderHookResult​

interface RenderHookResult<Result, Props> {
result: { current: Result };
rerender: (props: Props) => void;
unmount: () => void;
}

The renderHook function returns an object that has the following properties:

result​

The current value of the result will reflect the latest of whatever is returned from the callback passed to renderHook. The Result type is determined by the type passed to or inferred by the renderHook call.

rerender​

A function to rerender the test component, causing any hooks to be recalculated. If newProps are passed, they will replace the callback function's initialProps for subsequent rerenders. The Props type is determined by the type passed to or inferred by the renderHook call.

unmount​

A function to unmount the test component. This is commonly used to trigger cleanup effects for useEffect hooks.

Examples​

Here we present some extra examples of using renderHook API.

With initialProps​

const useCount = (initialCount: number) => {
const [count, setCount] = useState(initialCount);
const increment = () => setCount((previousCount) => previousCount + 1);

useEffect(() => {
setCount(initialCount);
}, [initialCount]);

return { count, increment };
};

it('should increment count', () => {
const { result, rerender } = renderHook((initialCount: number) => useCount(initialCount), {
initialProps: 1,
});

expect(result.current.count).toBe(1);

act(() => {
result.current.increment();
});

expect(result.current.count).toBe(2);
rerender(5);
expect(result.current.count).toBe(5);
});

With wrapper​

it('should use context value', () => {
function Wrapper({ children }: { children: ReactNode }) {
return <Context.Provider value="provided">{children}</Context.Provider>;
}

const { result } = renderHook(() => useHook(), { wrapper: Wrapper });
// ...
});

Configuration​

configure​

type Config = {
asyncUtilTimeout: number;
defaultHidden: boolean;
defaultDebugOptions: Partial<DebugOptions>;
};

function configure(options: Partial<Config>) {}

asyncUtilTimeout option​

Default timeout, in ms, for async helper functions (waitFor, waitForElementToBeRemoved) and findBy* queries. Defaults to 1000 ms.

defaultIncludeHiddenElements option​

Default value for includeHiddenElements query option for all queries. The default value is set to false, so all queries will not match elements hidden from accessibility. This is because the users of the app would not be able to see such elements.

This option is also available as defaultHidden alias for compatibility with React Testing Library.

defaultDebugOptions option​

Default debug options to be used when calling debug(). These default options will be overridden by the ones you specify directly when calling debug().

resetToDefaults()​

function resetToDefaults() {}

Environment variables​

RNTL_SKIP_AUTO_CLEANUP​

Set to true to disable automatic cleanup() after each test. It works the same as importing react-native-testing-library/dont-cleanup-after-each or using react-native-testing-library/pure.

$ RNTL_SKIP_AUTO_CLEANUP=true jest

RNTL_SKIP_AUTO_DETECT_FAKE_TIMERS​

Set to true to disable auto-detection of fake timers. This might be useful in rare cases when you want to use non-Jest fake timers. See issue #886 for more details.

$ RNTL_SKIP_AUTO_DETECT_FAKE_TIMERS=true jest

Accessibility​

isHiddenFromAccessibility​

function isHiddenFromAccessibility(
element: ReactTestInstance | null
): boolean {}

Also available as isInaccessible() alias for React Testing Library compatibility.

Checks if given element is hidden from assistive technology, e.g. screen readers.

note

Like isInaccessible function from DOM Testing Library this function considers both accessibility elements and presentational elements (regular Views) to be accessible, unless they are hidden in terms of host platform.

This covers only part of ARIA notion of Accessiblity Tree, as ARIA excludes both hidden and presentational elements from the Accessibility Tree.

For the scope of this function, element is inaccessible when it, or any of its ancestors, meets any of the following conditions:

Specifying accessible={false}, accessiblityRole="none", or importantForAccessibility="no" props does not cause the element to become inaccessible.

+ \ No newline at end of file diff --git a/docs/eslint-plugin-testing-library.html b/docs/eslint-plugin-testing-library.html index bc133ada..47763524 100644 --- a/docs/eslint-plugin-testing-library.html +++ b/docs/eslint-plugin-testing-library.html @@ -4,13 +4,13 @@ ESLint Plugin Testing Library Compatibility | React Native Testing Library - +

ESLint Plugin Testing Library Compatibility

Most of the rules of the eslint-plugin-testing-library are compatible with this library except the following:

  • prefer-user-event: userEvent requires a DOM environment so it is not compatible with this library

Also, some rules have become useless, unless maybe you're using an old version of the library:

To get the rule consistent-data-testid to work, you need to configure it to check the testID attribute by adding the following in your eslint config file, the testIdPattern being whichever pattern you want to enforce:

{
"testing-library/consistent-data-testid": [
2,
{
"testIdAttribute": ["testID"],
"testIdPattern": "^TestId(__[A-Z]*)?$"
}
]
}
- + \ No newline at end of file diff --git a/docs/faq.html b/docs/faq.html index 4b818d8c..adab8893 100644 --- a/docs/faq.html +++ b/docs/faq.html @@ -4,7 +4,7 @@ FAQ | React Native Testing Library - + @@ -14,7 +14,7 @@ or iOS simulator/Android emulator to provision the underlying OS and platform AP using React Test Renderer while providing queries and event APIs (userEvent, fireEvent) that mimicking certain behaviors from the actual runtime.

You can learn more about our testing environment here.

This approach has specific benefits and shortfalls. On the positive side:

  • it allows testing most of the logic of regular React Native apps
  • it allows running tests on any OS supported by Jest or other test runners, e.g., on CI
  • it uses much less resources than full runtime simulation
  • you can use Jest fake timers

On the negative side:

  • you cannot test native features
  • it might not perfectly simulate certain JavaScript features, but we are working on it

The User Event interactions solve some of the simulation issues, as they offer more realistic event handling than the basic Fire Event API.

Should I use/migrate to screen queries?​

There is no need to migrate existing test code to use screen-bases queries. You can still use queries and other functions returned by render. The screen object captures the latest render result.

For new code, you are encouraged to use screen as there are some good reasons for that, which are described in this article by Kent C. Dodds.

Should I use/migrate to User Event interactions?​

We encourage you to migrate existing tests to use the User Event interactions, which offer more realistic event handling than the basic Fire Event API. Hence, it will provide more confidence in the quality of your code.

- + \ No newline at end of file diff --git a/docs/getting-started.html b/docs/getting-started.html index 4175b11a..5353a9e6 100644 --- a/docs/getting-started.html +++ b/docs/getting-started.html @@ -4,13 +4,13 @@ Getting Started | React Native Testing Library - +

Getting Started

The problem​

You want to write maintainable tests for your React Native components. As a part of this goal, you want your tests to avoid including implementation details of your components and focus on making your tests give you the confidence they are intended. As part of this, you want your tests to be maintainable in the long run so refactors of your components (changes to implementation but not functionality) don't break your tests and slow you and your team down.

This solution​

The React Native Testing Library (RNTL) is a lightweight solution for testing React Native components. It provides light utility functions on top of React Test Renderer, in a way that encourages better testing practices. Its primary guiding principle is:

The more your tests resemble how your software is used, the more confidence they can give you.

This project is inspired by React Testing Library. It is tested to work with Jest, but it should work with other test runners as well.

You can find the source of the QuestionsBoard component and this example here.

Installation​

Open a Terminal in your project's folder and run:

Using yarn​

yarn add --dev @testing-library/react-native

Using npm​

npm install --save-dev @testing-library/react-native

This library has a peer dependency for react-test-renderer package. Make sure that your react-test-renderer version matches exactly your react version.

info

To properly use helpers for async tests (findBy queries and waitFor), you need at least React >=16.9.0 (featuring async act) or React Native >=0.61 (which comes with React >=16.9.0).

Additional Jest matchers​

To use additional React Native-specific Jest matchers, add the following line to your jest-setup.ts file (configured using setupFilesAfterEnv):

import '@testing-library/react-native/extend-expect';

Flow​

Note for Flow users – you'll also need to install typings for react-test-renderer:

flow-typed install react-test-renderer

Example​

import { render, screen, fireEvent } from '@testing-library/react-native';
import { QuestionsBoard } from '../QuestionsBoard';

test('form submits two answers', () => {
const allQuestions = ['q1', 'q2'];
const mockFn = jest.fn();

render(<QuestionsBoard questions={allQuestions} onSubmit={mockFn} />);

const answerInputs = screen.getAllByLabelText('answer input');

fireEvent.changeText(answerInputs[0], 'a1');
fireEvent.changeText(answerInputs[1], 'a2');
fireEvent.press(screen.getByText('Submit'));

expect(mockFn).toBeCalledWith({
1: { q: 'q1', a: 'a1' },
2: { q: 'q2', a: 'a2' },
});
});

You can find the source of the QuestionsBoard component and this example here.

- + \ No newline at end of file diff --git a/docs/how-should-i-query.html b/docs/how-should-i-query.html index 14e21d42..695e43e0 100644 --- a/docs/how-should-i-query.html +++ b/docs/how-should-i-query.html @@ -4,13 +4,13 @@ How Should I Query? | React Native Testing Library - +

How Should I Query?

React Native Testing Library provides various query types, allowing great flexibility in finding views appropriate for your tests. At the same time, the number of queries might be confusing. This guide aims to help you pick the correct queries for your test scenarios.

Query parts​

Each query is composed of two parts: variant and predicate, which are separated by the by word in the middle of the query.

Consider the following query:

getByRole()

For this query, getBy* is the query variant, and *ByRole is the predicate.

Query variant​

The query variants describe the expected number (and timing) of matching elements, so they differ in their return type.

VariantAssertionReturn typeIs Async?
getBy*Exactly one matching elementReactTestInstanceNo
getAllBy*At least one matching elementArray<ReactTestInstance>No
queryBy*Zero or one matching elementReactTestInstance | nullNo
queryAllBy*No assertionArray<ReactTestInstance>No
findBy*Exactly one matching elementPromise<ReactTestInstance>Yes
findAllBy*At least one matching elementPromise<Array<ReactTestInstance>>Yes

Queries work as implicit assertions on the number of matching elements and will throw an error when the assertion fails.

Idiomatic query variants​

Idiomatic query variants clarify test intent and the expected number of matching elements. They will also throw helpful errors if assertions fail to help diagnose the issues.

Here are general guidelines for picking idiomatic query variants:

  1. Use getBy* in the most common case when you expect a single matching element. Use other queries only in more specific cases.
  2. Use findBy* when an element is not yet in the element tree, but you expect it to be there as a result of some asynchronous action.
  3. Use getAllBy* (and findAllBy* for async) if you expect more than one matching element, e.g. in a list.
  4. Use queryBy* only when element should not exist to use it together with e.g. not.toBeOnTheScreen() matcher.

Avoid using queryAllBy* in regular tests, as it provides no assertions on the number of matching elements. You may still find it useful when building reusable custom testing tools.

Query predicate​

The query predicate describes how you decide whether to match the given element.

PredicateSupported elementsInspected props
*ByRoleall host elementsrole, accessibilityRole,
optional: accessible name, accessibility state and value
*ByLabelTextall host elementsaria-label, aria-labelledby,
accessibilityLabel, accessibilityLabelledBy
*ByDisplayValueTextInputvalue, defaultValue
*ByPlaceholderTextTextInputplaceholder
*ByTextTextchildren (text content)
*ByHintTextall host elementsaccessibilityHint
*ByTestIdall host elementstestID

Idiomatic query predicates​

Choosing the proper query predicate helps better express the test's intent and make the tests resemble how users interact with your code (components, screens, etc.) as much as possible following our Guiding Principles. Additionally, most predicates promote the usage of proper accessibility props, which add a semantic layer on top of an element tree composed primarily of View elements.

It is recommended to use query predicates in the following order of priority:

1. By Role query​

The first and most versatile predicate is *ByRole, which starts with the semantic role of the element and can be further narrowed down with additional options. React Native has two role systems, the web/ARIA-compatible one based on role prop and the traditional one based on accessibilityRole prop, you can use either of these.

In most cases, you need to set accessibility roles explicitly (or your component library can set some of them for you). These roles allow assistive technologies (like screen readers) and testing code to understand your view hierarchy better.

Some frequently used roles include:

  • alert - important text to be presented to the user, e.g., error message
  • button
  • checkbox & switch - on/off controls
  • heading (header) - header for content section, e.g., the title of navigation bar
  • img (image)
  • link
  • menu & menuitem
  • progressbar
  • radiogroup & radio
  • searchbox (search)
  • slider (adjustable)
  • summary
  • tablist & tab
  • text - static text that cannot change
  • toolbar - container for action buttons

Name option​

Frequently, you will want to add the name option, which will match both the element's role and its accessible name (= element's accessibility label or text content).

Here are a couple of examples:

  • start button: getByRole("button", { name: "Start" })
  • silent mode switch: getByRole("switch", { name: "Silent Mode" })
  • screen header: getByRole("header", { name: "Settings" })
  • undo menu item: getByRole("menuitem", { name: "Undo" })
  • error messages: getByRole("alert", { name: /Not logged in/ })

2. Text input queries​

Querying TextInput elements presents a unique challenge as there is no separate role for TextInput elements. There is a searchbox/search role, which can be assigned to TextInput, but it should be only used in the context of search inputs, leaving other text inputs without a role to query with.

Therefore, you can use the following queries to find relevant text inputs:

  1. *ByLabelText - will match the accessibility label of the element. This query will match any host elements, including TextInput elements.
  2. *ByPlaceholderText - will match the placeholder of TextInput element. This query will match only TextInput elements.
  3. *ByDisplayValue - will the current (or default) value of TextInput element. This query will match only TextInput elements.

3. Other accessible queries​

These queries reflect the apps' user experience, both visual and through assistive technologies (e.g. screen reader).

These queries include:

  • *ByText - will match the text content of the element. This query will match only Text elements.
  • *ByLabelText - will match the accessibility label of the element.
  • *ByHintText - will match the accessibility hint of the element.

4. Test ID query​

As a final predicate, you can use the testID prop to find relevant views. Using the *ByTestId predicate offers the most flexibility, but at the same time, it does not represent the user experience, as users are not aware of test IDs.

Note that using test IDs is a widespread technique in end-to-end testing due to various issues with querying views through other means in its specific context. Nevertheless, we still encourage you to use recommended RNTL queries as it will make your integration and component test more reliable and resilient.

- + \ No newline at end of file diff --git a/docs/jest-matchers.html b/docs/jest-matchers.html index fcf518d5..3cf56ca3 100644 --- a/docs/jest-matchers.html +++ b/docs/jest-matchers.html @@ -4,13 +4,13 @@ Jest Matchers | React Native Testing Library - +

Jest Matchers

note

Built-in Jest matchers require RNTL v12.4.0 or later.

This guide describes built-in Jest matchers, we recommend using these matchers as they provide readable tests, accessibility support, and a better developer experience.

Setup​

You can use the built-in matchers by adding the following line to your jest-setup.ts file (configured using setupFilesAfterEnv):

import '@testing-library/react-native/extend-expect';

Alternatively, you can add above script to your Jest configuration (usually located either in the jest.config.js file or in the package.json file under the "jest" key):

{
"preset": "react-native",
"setupFilesAfterEnv": ["@testing-library/react-native/extend-expect"]
}

Migration from legacy Jest Native matchers.​

If you are already using legacy Jest Native matchers we have a migration guide for moving to the built-in matchers.

Checking element existence​

toBeOnTheScreen()​

expect(element).toBeOnTheScreen();

This allows you to assert whether an element is attached to the element tree or not. If you hold a reference to an element and it gets unmounted during the test it will no longer pass this assertion.

Element Content​

toHaveTextContent()​

expect(element).toHaveTextContent(
text: string | RegExp,
options?: {
exact?: boolean;
normalizer?: (text: string) => string;
},
)

This allows you to assert whether the given element has the given text content or not. It accepts either string or RegExp matchers, as well as text match options of exact and normalizer.

toContainElement()​

expect(container).toContainElement(
element: ReactTestInstance | null,
)

This allows you to assert whether the given container element does contain another host element.

toBeEmptyElement()​

expect(element).toBeEmptyElement();

This allows you to assert whether the given element does not have any host child elements or text content.

Checking element state​

toHaveDisplayValue()​

expect(element).toHaveDisplayValue(
value: string | RegExp,
options?: {
exact?: boolean;
normalizer?: (text: string) => string;
},
)

This allows you to assert whether the given TextInput element has a specified display value. It accepts either string or RegExp matchers, as well as text match options of exact and normalizer.

toHaveAccessibilityValue()​

expect(element).toHaveAccessibilityValue(
value: {
min?: number;
max?: number;
now?: number;
text?: string | RegExp;
},
)

This allows you to assert whether the given element has a specified accessible value.

This matcher will assert accessibility value based on aria-valuemin, aria-valuemax, aria-valuenow, aria-valuetext and accessibilityValue props. Only defined value entries will be used in the assertion, the element might have additional accessibility value entries and still be matched.

When querying by text entry a string or RegExp might be used.

toBeEnabled() / toBeDisabled​

expect(element).toBeEnabled();
expect(element).toBeDisabled();

These allow you to assert whether the given element is enabled or disabled from the user's perspective. It relies on the accessibility disabled state as set by aria-disabled or accessibilityState.disabled props. It will consider a given element disabled when it or any of its ancestors is disabled.

note

These matchers are the negation of each other, and both are provided to avoid double negations in your assertions.

toBeSelected()​

expect(element).toBeSelected();

This allows you to assert whether the given element is selected from the user's perspective. It relies on the accessibility selected state as set by aria-selected or accessibilityState.selected props.

toBeChecked() / toBePartiallyChecked()​

expect(element).toBeChecked();
expect(element).toBePartiallyChecked();

These allow you to assert whether the given element is checked or partially checked from the user's perspective. It relies on the accessibility checked state as set by aria-checked or accessibilityState.checked props.

note
  • toBeChecked() matcher works only on elements with the checkbox or radio role.
  • toBePartiallyChecked() matcher works only on elements with the checkbox role.

toBeExpanded() / toBeCollapsed()​

expect(element).toBeExpanded();
expect(element).toBeCollapsed();

These allows you to assert whether the given element is expanded or collapsed from the user's perspective. It relies on the accessibility disabled state as set by aria-expanded or accessibilityState.expanded props.

note

These matchers are the negation of each other for expandable elements (elements with explicit aria-expanded or accessibilityState.expanded props). However, both won't pass for non-expandable elements (ones without explicit aria-expanded or accessibilityState.expanded props).

toBeBusy()​

expect(element).toBeBusy();

This allows you to assert whether the given element is busy from the user's perspective. It relies on the accessibility selected state as set by aria-busy or accessibilityState.busy props.

Checking element style​

toBeVisible()​

expect(element).toBeVisible();

This allows you to assert whether the given element is visible from the user's perspective.

The element is considered invisible when itself or any of its ancestors has display: none or opacity: 0 styles, as well as when it's hidden from accessibility.

toHaveStyle()​

expect(element).toHaveStyle(
style: StyleProp<Style>,
)

This allows you to assert whether the given element has given styles.

Other matchers​

toHaveAccessibleName()​

expect(element).toHaveAccessibleName(
name?: string | RegExp,
options?: {
exact?: boolean;
normalizer?: (text: string) => string;
},
)

This allows you to assert whether the given element has a specified accessible name. It accepts either string or RegExp matchers, as well as text match options of exact and normalizer.

The accessible name will be computed based on aria-labelledby, accessibilityLabelledBy, aria-label, and accessibilityLabel props, in the absence of these props, the element text content will be used.

When the name parameter is undefined it will only check if the element has any accessible name.

toHaveProp()​

expect(element).toHaveProp(
name: string,
value?: unknown,
)

This allows you to assert whether the given element has a given prop. When the value parameter is undefined it will only check for existence of the prop, and when value is defined it will check if the actual value matches passed value.

note

This matcher should be treated as an escape hatch to be used when all other matchers are not suitable.

- + \ No newline at end of file diff --git a/docs/migration-jest-native.html b/docs/migration-jest-native.html index 1e255f2c..6dd0bb8d 100644 --- a/docs/migration-jest-native.html +++ b/docs/migration-jest-native.html @@ -4,13 +4,13 @@ Migration from Jest Native matchers | React Native Testing Library - +

Migration from Jest Native matchers

This guide describes the steps necessary to migrate from legacy Jest Native matchers v5 to built-in Jest matchers.

General notes​

All of the built-in Jest matchers provided by the React Native Testing Library support only host elements. This should not be an issue, as all RNTL v12 queries already return only host elements. When this guide states that a given matcher should work the same it assumes behavior only host elements. If you need to assert the status of composite elements use Jest Native matchers in legacy mode.

Usage​

You can use the built-in matchers by adding the following line to your jest-setup.ts file (configured using setupFilesAfterEnv):

import '@testing-library/react-native/extend-expect';

Gradual migration​

You can use the built-in matchers alongside legacy Jest Native matchers by changing their import in your jest-setup.ts file:

// Replace this:
// import '@testing-library/jest-native/extend-expect';

// With this:
import '@testing-library/react-native/extend-expect';
import '@testing-library/jest-native/legacy-extend-expect';

In this case legacy matchers will be available using the legacy_ prefix, e.g.:

expect(element).legacy_toHaveAccessibilityState({ busy: true });

Migration details​

Matchers not requiring changes​

The following matchers should work the same:

Replaced matchers​

The toHaveAccessibilityState() matcher has been replaced by the following matchers:

The new matchers support both accessibilityState and aria-* props.

Added matchers​

New toHaveAccessibleName() has been added.

Noteworthy details​

You should be aware of the following details:

  • toBeEnabled() / toBeDisabled() matchers also check the disabled state for the element's ancestors and not only the element itself. This is the same as in legacy Jest Native matchers of the same name but differs from the removed toHaveAccessibilityState() matcher.
  • toBeChecked() matcher supports only elements with a checkbox or radio role
  • toBePartiallyChecked() matcher supports only elements with checkbox role
- + \ No newline at end of file diff --git a/docs/migration-v11.html b/docs/migration-v11.html index 0309b139..272b3878 100644 --- a/docs/migration-v11.html +++ b/docs/migration-v11.html @@ -4,13 +4,13 @@ Migration to 11.0 | React Native Testing Library - +
-

Migration to 11.0

Migration to React Native Testing Library version 11 from version 9.x or 10.x should be a relatively easy task due small amount of breaking changes.

Breaking changes

Update to Jest 28 if you use fake timers​

If you use fake timers in any of your tests you should update your Jest dependencies to version 28. This is due to the fact that jest.useFakeTimers() config structure has changed.

Refactor legacy waitForOptions position​

In version 9 we introducted query options parameters for each query type. This affected all findBy and findAllBy queries because their signatures changed e.g. from:

function findByText(text: TextMatch, waitForOptions?: WaitForOptions)
function findAllByText(text: TextMatch, waitForOptions?: WaitForOptions)

to

function findByText(text: TextMatch, options?: TextMatchOptions, waitForOptions?: WaitForOptions)
function findAllByText(text: TextMatch, options?: TextMatchOptions, waitForOptions?: WaitForOptions)

In order to facilitate transition, in version 9 and 10, we provided a temporary possibility to pass WaitForOptions like timeout, interval, etc inside options argument. From this release we require passing these as the proper third parameter.

This change is easy to implement:

findByText(/Text/, { timeout: 1000 })

should become

findByText(/Text/, {}, { timeout: 1000 })

Triggering non-touch events on targets with pointerEvents="box-none" prop​

Up to version 10, RNTL disables all events for a target with pointerEvents="box-none". This behavior is counter to how React Native itself functions.

From version 11, RNTL continues to disable press event for these targets but allows triggering other events, e.g. layout.

All changes​

Full Changelog​

https://github.com/callstack/react-native-testing-library/compare/v10.1.1...v11.0.0

- +

Migration to 11.0

Migration to React Native Testing Library version 11 from version 9.x or 10.x should be a relatively easy task due small amount of breaking changes.

Breaking changes

Update to Jest 28 if you use fake timers​

If you use fake timers in any of your tests you should update your Jest dependencies to version 28. This is due to the fact that jest.useFakeTimers() config structure has changed.

Refactor legacy waitForOptions position​

In version 9 we introducted query options parameters for each query type. This affected all findBy and findAllBy queries because their signatures changed e.g. from:

function findByText(text: TextMatch, waitForOptions?: WaitForOptions)
function findAllByText(text: TextMatch, waitForOptions?: WaitForOptions)

to

function findByText(text: TextMatch, options?: TextMatchOptions, waitForOptions?: WaitForOptions)
function findAllByText(text: TextMatch, options?: TextMatchOptions, waitForOptions?: WaitForOptions)

In order to facilitate transition, in version 9 and 10, we provided a temporary possibility to pass WaitForOptions like timeout, interval, etc inside options argument. From this release we require passing these as the proper third parameter.

This change is easy to implement:

findByText(/Text/, { timeout: 1000 });

should become

findByText(/Text/, {}, { timeout: 1000 });

Triggering non-touch events on targets with pointerEvents="box-none" prop​

Up to version 10, RNTL disables all events for a target with pointerEvents="box-none". This behavior is counter to how React Native itself functions.

From version 11, RNTL continues to disable press event for these targets but allows triggering other events, e.g. layout.

All changes​

Full Changelog​

https://github.com/callstack/react-native-testing-library/compare/v10.1.1...v11.0.0

+ \ No newline at end of file diff --git a/docs/migration-v12.html b/docs/migration-v12.html index 8384b487..40e44edb 100644 --- a/docs/migration-v12.html +++ b/docs/migration-v12.html @@ -4,14 +4,14 @@ Migration to 12.0 | React Native Testing Library - +

Migration to 12.0

note

From v12.4:

If you are already using legacy Jest Native matchers we have a migration guide for moving to the built-in matchers.

Before v12.4: -If you use Jest Native matchers, which we recommend, then you should upgrade it to version 5.4.2 or higher.

React Native Testing Library 12 introduces a handful of breaking changes compared to 11.x versions. We believe they were necessary to improve the experience using the library and help the users fall into the pit of success when writing meaningful tests. You will find migration instructions for each and every change described below.

Breaking changes​

1. All queries exclude elements hidden from accessibility by default​

Elements that are hidden from accessiblity, e.g. elements on non-active screen when using React Navigation, now will not be matched by default by all queries. This is the effect of switching the default value for global config option defaultIncludeHiddenElements(api#defaultincludehiddenelements-option) to false.

Previous behaviour of matching hidden elements can be enabled on query level using includeHiddenElements query options or globally using defaultIncludeHiddenElements(api#defaultincludehiddenelements-option) configuration option.

2. *ByRole queries now return only accessibility elements​

*ByRole queries now return only accessibility elements, either explicitly marked with accessible prop or implicit ones where this status is derived from component type itself (e.g Text, TextInput, Switch, but not View).

You may need to adjust relevant components under test to make sure they pass isAccessibilityElement check.

Examples​

Let's assume we are using getByRole("button") query.

Following elements will match:

// Explicit "accessible" prop for View
<View accessible accessibilityRole="button" />

// No need to "accessible" prop for Text, as it is implicitly accessible element.
<Text accessibilityRole="button">Button</Text>

While following elements will not match:

// Missing "accessible" prop for View
<View accessibilityRole="button" />

// Explicit "accessible={false}" prop for View
<View accessible={false} accessibilityRole="button" />

// Explicit "accessible={false}" for Text, which is implicitly accessible element
<Text accessible={false} accessibilityRole="button">Button</Text>

3. *ByText, *ByDisplayValue, *ByPlaceholderText queries now return host elements​

*ByText, *ByDisplayValue, *ByPlaceholderText queries now return host elements, which is consistent with other queries.

While potentially breaking, this should not cause issues in tests if you are using recommended queries and Jest Matchers from Jest Native package.

Problematic cases may include: directly checking some prop values (without using Jest Native matchers), referencing other nodes using parent or children props, examining type property of ReactTestInstance, etc.

4. container API has been renamed to UNSAFE_root.​

Historically container was supposed to mimic the RTL's container. However it turned out not so relevant in RNTL's environment, where we actually used it to return React Test Renderer's root instance.

RNTL v12 introduces root API as an alternative that returns a root host element. The difference between root and UNSAFE_root properties is that that root will always represents a host element, while UNSAFE_root will typically represent a composite element.

If you use toBeOnTheScreen matcher from @testing-library/jest-native your tests will fail because it uses the container api. To fix this, update @testing-library/jest-native to version 5.4.2.

Full Changelog​

https://github.com/callstack/react-native-testing-library/compare/v11.5.2...v12.0.0

- +If you use Jest Native matchers, which we recommend, then you should upgrade it to version 5.4.2 or higher.

React Native Testing Library 12 introduces a handful of breaking changes compared to 11.x versions. We believe they were necessary to improve the experience using the library and help the users fall into the pit of success when writing meaningful tests. You will find migration instructions for each and every change described below.

Breaking changes​

1. All queries exclude elements hidden from accessibility by default​

Elements that are hidden from accessiblity, e.g. elements on non-active screen when using React Navigation, now will not be matched by default by all queries. This is the effect of switching the default value for global config option defaultIncludeHiddenElements(api#defaultincludehiddenelements-option) to false.

Previous behaviour of matching hidden elements can be enabled on query level using includeHiddenElements query options or globally using defaultIncludeHiddenElements(api#defaultincludehiddenelements-option) configuration option.

2. *ByRole queries now return only accessibility elements​

*ByRole queries now return only accessibility elements, either explicitly marked with accessible prop or implicit ones where this status is derived from component type itself (e.g Text, TextInput, Switch, but not View).

You may need to adjust relevant components under test to make sure they pass isAccessibilityElement check.

Examples​

Let's assume we are using getByRole("button") query.

Following elements will match:

// Explicit "accessible" prop for View
<View accessible accessibilityRole="button" />

// No need to "accessible" prop for Text, as it is implicitly accessible element.
<Text accessibilityRole="button">Button</Text>

While following elements will not match:

// Missing "accessible" prop for View
<View accessibilityRole="button" />

// Explicit "accessible={false}" prop for View
<View accessible={false} accessibilityRole="button" />

// Explicit "accessible={false}" for Text, which is implicitly accessible element
<Text accessible={false} accessibilityRole="button">Button</Text>

3. *ByText, *ByDisplayValue, *ByPlaceholderText queries now return host elements​

*ByText, *ByDisplayValue, *ByPlaceholderText queries now return host elements, which is consistent with other queries.

While potentially breaking, this should not cause issues in tests if you are using recommended queries and Jest Matchers from Jest Native package.

Problematic cases may include: directly checking some prop values (without using Jest Native matchers), referencing other nodes using parent or children props, examining type property of ReactTestInstance, etc.

4. container API has been renamed to UNSAFE_root.​

Historically container was supposed to mimic the RTL's container. However it turned out not so relevant in RNTL's environment, where we actually used it to return React Test Renderer's root instance.

RNTL v12 introduces root API as an alternative that returns a root host element. The difference between root and UNSAFE_root properties is that that root will always represents a host element, while UNSAFE_root will typically represent a composite element.

If you use toBeOnTheScreen matcher from @testing-library/jest-native your tests will fail because it uses the container api. To fix this, update @testing-library/jest-native to version 5.4.2.

Full Changelog​

https://github.com/callstack/react-native-testing-library/compare/v11.5.2...v12.0.0

+ \ No newline at end of file diff --git a/docs/migration-v2.html b/docs/migration-v2.html index 3402fd80..c4c927cc 100644 --- a/docs/migration-v2.html +++ b/docs/migration-v2.html @@ -4,13 +4,13 @@ Migration to 2.0 | React Native Testing Library - +

Migration to 2.0

This guide describes steps necessary to migrate from React Native Testing Library v1.x to v2.0.

Dropping Node 8​

Node 8 reached its EOL more than 5 months ago, so it's about time to target the library to Node 10. If you used lower version, you'll have to upgrade to v10, but we recommend using the latest LTS version.

Auto Cleanup​

cleanup() function is now called automatically after every test if your testing framework supports afterEach hook (like Jest, Mocha, and Jasmine).

You should be able to remove all afterEach(cleanup) calls in your code.

This change might break your code, if you tests are not isolated, i.e. you call render outside test block. Generally, you should keep your tests isolated. But if you can't or don't want to do this right away you can prevent this behavior using any of the following ways:

  • by importing 'react-native-testing-library/pure' instead of 'react-native-testing-library'

  • by importing 'react-native-testing-library/dont-cleanup-after-each' before importing 'react-native-testing-library'. You can do it in a global way by using Jest's setupFiles like this:

    {
    "setupFiles": ["react-native-testing-library/dont-cleanup-after-each"];
    }
  • by setting RNTL_SKIP_AUTO_CLEANUP env variable to true. You can do this with cross-evn like this:

    cross-env RNTL_SKIP_AUTO_CLEANUP=true jest

WaitFor API changes​

We renamed waitForElement function to waitFor for consistency with React Testing Library. Additionally, the signature has slightly changed from:

export default function waitForElement<T>(
expectation: () => T,
timeout?: number,
interval?: number
): Promise<T> {}

to:

export default function waitFor<T>(
expectation: () => T,
options: {
timeout?: number,
interval?: number,
}
): Promise<T> {}

Both changes should improve code readibility.

waitFor calls (and hence also findBy queries) are now wrapped in act by default, so that you should no longer need to use act directly in your tests.

tip

You can usually avoid waitFor by a proper use of findBy asynchronous queries. It will result in more streamlined testing experience.

Removed global debug function​

The debug() method returned from render() function is all you need. We removed the global export to avoid confusion.

Removed global shallow function​

Shallow rendering React component is usually not a good idea, so we decided to remove the API. But, if you find it useful or need to support legacy tests, feel free to use this implementation:

import ShallowRenderer from 'react-test-renderer/shallow';

export function shallow(instance: ReactTestInstance | React.Element<any>) {
const renderer = new ShallowRenderer();
renderer.render(React.createElement(instance.type, instance.props));

return { output: renderer.getRenderOutput() };
}

Removed functions​

Following query functions have been removed after being deprecated for more than a year now:

  • getByName
  • getAllByName
  • queryByName
  • queryAllByName

The *ByType and *ByProps queries has been prefixed with UNSAFE_. These UNSAFE_ functions are not planned for removal in future versions but their usage is discouraged. You can rename them using global search/replace in your project:

  • getByType -> UNSAFE_getByType
  • getAllByType -> UNSAFE_getAllByType
  • queryByType -> UNSAFE_queryByType
  • queryAllByType -> UNSAFE_queryAllByType
  • getByProps -> UNSAFE_getByProps
  • getAllByProps -> UNSAFE_getAllByProps
  • queryByProps -> UNSAFE_queryByProps
  • queryAllByProps -> UNSAFE_queryAllByProps

Some ByTestId queries behavior changes​

In version 1.x the getByTestId and queryByTestId queries could return non-native instances. This was a serious bug. Other query functions like getAllByTestId, queryAllByTestId, findByTestId and findAllByTestId didn't have this issue. These correctly returned only native components instances (e.g. View, Text, etc) that got the testID.

In v2 we fixed this inconsistency, which may result in failing tests, if you relied on this behavior. There are few ways to handle these failures:

  • pass the testID prop down so it can reach a native component, like View or Text
  • replace testID with proper accessibilityHint or accessibilityLabel if it benefits the user
  • use safe queries like *ByText or *ByA11yHint

Deprecated flushMicrotasksQueue​

We have deprecated flushMicrotasksQueue and plan to remove it in the next major. We have better alternatives available for helping you write async tests – findBy async queries and waitFor helper.

If you can't or don't want to migrate your tests, don't worry. You can use the same implementation we have today:

function flushMicrotasksQueue() {
return new Promise((resolve) => setImmediate(resolve));
}
- + \ No newline at end of file diff --git a/docs/migration-v7.html b/docs/migration-v7.html index 558f29c9..a874fce0 100644 --- a/docs/migration-v7.html +++ b/docs/migration-v7.html @@ -4,13 +4,13 @@ Migration to 7.0 | React Native Testing Library - +

Migration to 7.0

caution

We renamed the react-native-testing-library npm package to @testing-library/react-native, officially joining the "Testing Library" family 🎉.

As the version 7.0 involves merging two libraries together, there are two variants for migration guide, dependent on library you used previously:

Guide for react-native-testing-library users​

This guide describes steps necessary to migrate from React Native Testing Library v2.x or v6.0 to v7.0.

Renaming the library​

  1. Install @testing-library/react-native.
  2. Uninstall react-native-testing-library.
  3. Rename all references of react-native-testing-library to @testing-library/react-native.

You may have noticed a strange v2 to v7 upgrade, skipping versions 3, 4, 5 and 6. This is because we renamed the react-native-testing-library npm package to @testing-library/react-native, officially joining the "Testing Library" family 🎉. We're merging existing two libraries into a single one. The native-testing-library repository, which had v6, will soon be archived and using @testing-library/react-native below v7, sourced from mentioned repository, is deprecated.

For branding purposes we keep the "React Native Testing Library" name, similar to "React Testing Library". Only the npm published package is changing. The code repository also stays the same under Callstack governance.

New aliases​

To improve compatibility with React Testing Library, and ease the migration for @testing-library/react-native users using version below v7, we've introduced new aliases to our accessibility queries:

  • ByLabelText aliasing ByA11yLabel queries
  • ByHintText aliasing ByA11yHint queries
  • ByRole aliasing ByA11yRole queries

We like the new names and consider removing the aliases in future releases.

Renaming ByPlaceholder queries​

To improve compatibility with React Testing Library, and to ease the migration for @testing-library/react-native users using version below v7, we've renamed following queries:

  • ByPlaceholder -> ByPlaceholderText

Please replace all occurrences of these queries in your codebase.

fireEvent support for disabled components​

To improve compatibility with the real React Native environment fireEvent now performs checks whether the component is "disabled" before firing an event on it. It uses the Responder system to establish should the event fire, which resembles the actual React Native runtime closer than we used to.

If your code contained any workarounds for preventing events firing on disabled events, you should now be able to remove them.

Guide for @testing-library/react-native users​

This guide describes steps necessary to migrate from @testing-library/react-native from v6.0 to v7.0. Although the name stays the same, this is a different library, sourced at Callstack GitHub repository. We made sure the upgrade path is as easy for you as possible.

Renaming "wait" helpers​

The wait and waitForElement helpers are replaced by waitFor. Please rename all occurrences of these in your codebase.

Changes to ByTestId queries​

The ByTestId queries don't accept RegExps. Please use strings instead. We're happy to accept PRs adding this functionality :).

No ByTitle queries​

Our library doesn't implement ByTitle queries, which are targetting components with title prop, specifically Button and RefreshControl. If your tests only use ByTitle to target Button components, you can replace them with ByText queries, since React Native renders Text under the hood.

If you need to query RefreshControl component and can't figure out other way around it, you can use e.g. UNSAFE_getByProps({title}) query.

No custom Jest configuration​

Use the official React Native preset for Jest:

{
"jest": {
- "preset": "@testing-library/react-native"
+ "preset": "react-native"
}
}

We're told this also speeds up your tests startup on cold cache. Using official preset has another benefit – the library is compatible with any version of React Native without introducing breaking changes.

Cleanup is included by default​

Cleaning up (unmounting) components after each test is included by default in the same manner as in React Testing Library. Please remove this setup file from Jest config:

{
"jest": {
- "setupFilesAfterEnv": ["@testing-library/react-native/cleanup-after-each"]
}
}

You can opt-out of this behavior by running tests with RNTL_SKIP_AUTO_CLEANUP=true flag or importing from @testing-library/react-native/pure. We encourage you to keep the default though.

No NativeTestInstance abstraction​

We don't provide any abstraction over ReactTestInstance returned by queries, but allow to use it directly to access queried component's props or type for that example.

No container nor baseElement returned from render​

There's no container returned from the render function. If you must, use react-test-renderer directly, although we advise against doing so. We also don't implement baseElement because of that, since there's no document.documentElement nor container.

Firing events changes​

There are slight differences in how fireEvent works in both libraries:

  1. Our library doesn't perform validation checks for events fired upon tested components.
  2. Signature is different:
    -fireEvent[eventName](node: FiberRoot, eventProperties: NativeTestEvent)
    +fireEvent(element: ReactTestInstance, eventName: string, ...data: Array<any>)
  3. There is no NativeTestEvent - second and rest arguments are used instead.
  4. There are only 3 short-hand events: fireEvent.press, fireEvent.changeText and fireEvent.scroll. For all other or custom events you can use the base signature.
- + \ No newline at end of file diff --git a/docs/migration-v9.html b/docs/migration-v9.html index c8e6fe96..7343499c 100644 --- a/docs/migration-v9.html +++ b/docs/migration-v9.html @@ -4,13 +4,13 @@ Migration to 9.0 | React Native Testing Library - +

Migration to 9.0

Version 7.0 brought React Native Testing Library into the @testing-library family. Since it has been implemented independently from its web counterpart – the React Testing Library – there are some differences in the API and behavior. Version 9.0 solves several of these problems.

Support for text match options a.k.a string precision API​

This is a backward compatible change.

When querying text, it is now possible to pass a TextMatch to most text based queries, which lets you configure how @testing-library/react-native should match your text. For instance, passing exact: false will allow matching substrings and will ignore case:

const { getByText } = render(<Text>Hello World</Text>);

getByText('Hello World'); // Matches
getByText('Hello'); // Doesn't match
getByText('hello', { exact: false }); // ignore case-sensitivity and does partial matching

Please note that the findBy* queries used to take a waitForOptions parameter as a second argument, which has now been moved to the third argument:

-findByText('Hello world', { timeout: 3000 }); // old findBy* API
+findByText('Hello world', {}, { timeout: 3000 }); // new findBy* API

For backward compatibility RNTL v9 can still read waitForOptions from the second argument but will print a deprecation warning.

Reverted matching text across several nodes​

caution

This is a breaking change.

In v1.14 we've introduced a feature allowing to match text when it's spread across several nodes:

const { getByText } = render(
<Text>
Hello <Text>world</Text>
</Text>
);
getByText('Hello world'); // matches

However this behavior was different than the web one, and wouldn't always be straightforward to reason about. For instance it could match text nodes far from each other on the screen. It also prevented us from implementing the string precision API. From v9, this type of match will not work.

A work around is to use within:

import {Text} from 'react-native'
import {render, within} from '@testing-library/react-native'

const {getByText} = render(<Text>Hello <Text>world</Text</Text>)

within(getByText('Hello', {exact: false})).getByText('world')

Future plans​

This release changes a lot of internal logic in the library, paving the way for more improvements to bring us closer to our web counterpart, with a possibly better story for accessibility queries.

We're also migrating the codebase to TypeScript. Please let us know if you're interested in helping us with this effort.

Stay safe!

- + \ No newline at end of file diff --git a/docs/react-navigation.html b/docs/react-navigation.html index e154f820..98682b4a 100644 --- a/docs/react-navigation.html +++ b/docs/react-navigation.html @@ -4,13 +4,13 @@ React Navigation | React Native Testing Library - +
-

React Navigation

caution

This examples has not been updated for a long time and not showcase current recommended testing practices. We plan to update this document soon.

This section deals with integrating @testing-library/react-native with react-navigation, using Jest.

Stack Navigator​

Setting up​

Install the packages required for React Navigation. For this example, we will use a stack navigator to transition to the second page when any of the items are clicked on.

$ yarn add @react-native-community/masked-view @react-navigation/native @react-navigation/stack react-native-gesture-handler react-native-reanimated react-native-safe-area-context react-native-screens

Create an ./AppNavigator.js component which will list the navigation stack:

import 'react-native-gesture-handler';
import * as React from 'react';
import { createStackNavigator } from '@react-navigation/stack';

import HomeScreen from './screens/HomeScreen';
import DetailsScreen from './screens/DetailsScreen';

const { Screen, Navigator } = createStackNavigator();

export default function Navigation() {
const options = {};

return (
<Navigator>
<Screen name="Home" component={HomeScreen} />
<Screen options={options} name="Details" component={DetailsScreen} />
</Navigator>
);
}

Create your two screens which we will transition to and from them. The homescreen, found in ./screens/HomeScreen.js, contains a list of elements presented in a list view. On tap of any of these items will move to the details screen with the item number:

import * as React from 'react';
import {
Text,
View,
FlatList,
TouchableOpacity,
StyleSheet,
} from 'react-native';

export default function HomeScreen({ navigation }) {
const [items] = React.useState(
new Array(20).fill(null).map((_, idx) => idx + 1)
);

const onOpacityPress = (item) => navigation.navigate('Details', item);

return (
<View>
<Text style={styles.header}>List of numbers from 1 to 20</Text>
<FlatList
keyExtractor={(_, idx) => `${idx}`}
data={items}
renderItem={({ item }) => (
<TouchableOpacity
onPress={() => onOpacityPress(item)}
style={styles.row}
>
<Text>Item number {item}</Text>
</TouchableOpacity>
)}
/>
</View>
);
}

const divider = '#DDDDDD';

const styles = StyleSheet.create({
header: {
fontSize: 20,
textAlign: 'center',
marginVertical: 16,
},
row: {
paddingVertical: 16,
paddingHorizontal: 24,
borderBottomColor: divider,
borderBottomWidth: 1,
},
});

The details screen, found in ./screens/DetailsScreen.js, contains a header with the item number passed from the home screen:

// ./screens/DetailsScreen.js
import * as React from 'react';
import { Text, StyleSheet, View } from 'react-native';

export default function DetailsScreen(props) {
const item = Number.parseInt(props.route.params, 10);

return (
<View>
<Text style={styles.header}>Showing details for {item}</Text>
<Text style={styles.body}>the number you have chosen is {item}</Text>
</View>
);
}

const styles = StyleSheet.create({
header: {
fontSize: 20,
textAlign: 'center',
marginVertical: 16,
},
body: {
textAlign: 'center',
},
});

Setting up the test environment​

Install required dev dependencies:

$ yarn add -D jest @testing-library/react-native

Create your jest.config.js file (or place the following properties in your package.json as a "jest" property)

module.exports = {
preset: 'react-native',
setupFiles: ['./node_modules/react-native-gesture-handler/jestSetup.js'],
transformIgnorePatterns: [
'node_modules/(?!(jest-)?@?react-native|@react-native-community|@react-navigation)',

// For pnpm you need to use inlcude `(?!(?:.pnpm/)?` part like this:
// 'node_modules/(?!(?:.pnpm/)?((jest-)?@?react-native|@react-native-community|@react-navigation))',
],
};

Notice the 2 entries that don't come with the default React Native project:

  • setupFiles – an array of files that Jest is going to execute before running your tests. In this case, we run react-native-gesture-handler/jestSetup.js which sets up necessary mocks for react-native-gesture-handler native module
  • transformIgnorePatterns – an array of paths that Jest ignores when transforming code. In this case, the negative lookahead regular expression is used, to tell Jest to transform (with Babel) every package inside node_modules/ that starts with react-native, @react-native-community or @react-navigation (added by us, the rest is in react-native preset by default, so you don't have to worry about it).

Example tests​

For this example, we are going to test out two things. The first thing is that the page is laid out as expected. The second, and most important, is that the page will transition to the detail screen when any item is tapped on.

Let's add a AppNavigator.test.js file in src/__tests__ directory:

import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { render, screen, fireEvent } from '@testing-library/react-native';

import AppNavigator from '../AppNavigator';

// Silence the warning https://github.com/facebook/react-native/issues/11094#issuecomment-263240420
// Use with React Native <= 0.63
jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper');

// Use this instead with React Native >= 0.64
// jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper');

describe('Testing react navigation', () => {
test('page contains the header and 10 items', async () => {
const component = (
<NavigationContainer>
<AppNavigator />
</NavigationContainer>
);

render(component);

const header = await screen.findByText('List of numbers from 1 to 20');
const items = await screen.findAllByText(/Item number/);

expect(header).toBeOnTheScreen();
expect(items.length).toBe(10);
});

test('clicking on one item takes you to the details screen', async () => {
const component = (
<NavigationContainer>
<AppNavigator />
</NavigationContainer>
);

render(component);
const toClick = await screen.findByText('Item number 5');

fireEvent(toClick, 'press');
const newHeader = await screen.findByText('Showing details for 5');
const newBody = await screen.findByText('the number you have chosen is 5');

expect(newHeader).toBeOnTheScreen();
expect(newBody).toBeOnTheScreen();
});
});

Drawer Navigator​

Testing the Drawer Navigation requires an additional setup step for mocking the Reanimated library.

Setting up​

Install the packages required for React Navigation. For this example, we will use a drawer navigator to transition between a home screen and an additional screen.

$ yarn add @react-native-community/masked-view @react-navigation/native @react-navigation/drawer react-native-gesture-handler react-native-reanimated react-native-safe-area-context react-native-screens

Create a ./DrawerAppNavigator.js component which will list the navigation stack:

import 'react-native-gesture-handler';
import React from 'react';
import { createDrawerNavigator } from '@react-navigation/drawer';

const { Screen, Navigator } = createDrawerNavigator();

export default function Navigation() {
return (
<Navigator>
<Screen name="Home" component={HomeScreen} />
<Screen name="Notifications" component={NotificationsScreen} />
</Navigator>
);
}

Create your two screens which we will transition to and from:

function HomeScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Welcome!</Text>
<Button
onPress={() => navigation.navigate('Notifications')}
title="Go to notifications"
/>
</View>
);
}

function NotificationsScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>This is the notifications screen</Text>
<Button onPress={() => navigation.goBack()} title="Go back home" />
</View>
);
}

Setting up the test environment​

Install required dev dependencies:

$ yarn add -D jest @testing-library/react-native

Create a mock file necessary for your tests:

import 'react-native-gesture-handler/jestSetup';

jest.mock('react-native-reanimated', () => {
const Reanimated = require('react-native-reanimated/mock');

// The mock for `call` immediately calls the callback which is incorrect
// So we override it with a no-op
Reanimated.default.call = () => {};

return Reanimated;
});

// Silence the warning: Animated: `useNativeDriver` is not supported because the native animated module is missing
jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper');

Create your jest.config.js file (or place the following properties in your package.json as a "jest" property)

module.exports = {
preset: 'react-native',
setupFilesAfterEnv: ['./jest-setup.js'],
transformIgnorePatterns: [
'node_modules/(?!(jest-)?react-native|@react-native-community|@react-navigation)',
],
};

Make sure that the path to the file in setupFiles is correct. Jest will run these files before running your tests, so it's the best place to put your global mocks.

This setup is copied from the React Navigation documentation.

Example tests​

For this example, we are going to test out two things. The first thing is that the screen is loaded correctly. The second, and most important, is that the page will transition to the notifications screen when the button is tapped on.

Let's add a DrawerAppNavigator.test.js file in src/__tests__ directory:

import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { render, screen, fireEvent } from '@testing-library/react-native';

import DrawerAppNavigator from '../DrawerAppNavigator';

describe('Testing react navigation', () => {
test('screen contains a button linking to the notifications page', async () => {
const component = (
<NavigationContainer>
<DrawerAppNavigator />
</NavigationContainer>
);

render(component);
const button = await screen.findByText('Go to notifications');

expect(button).toBeOnTheScreen();
});

test('clicking on the button takes you to the notifications screen', async () => {
const component = (
<NavigationContainer>
<DrawerAppNavigator />
</NavigationContainer>
);

render(component);
const oldScreen = screen.queryByText('Welcome!');
const button = await screen.findByText('Go to notifications');

expect(oldScreen).toBeOnTheScreen();

fireEvent(button, 'press');
const newScreen = await screen.findByText(
'This is the notifications screen'
);

expect(newScreen).toBeOnTheScreen();
});
});

Running tests​

To run the tests, place a test script inside your package.json

{
"scripts": {
"test": "jest"
}
}

And run the test script with npm test or yarn test.

- +

React Navigation

caution

This examples has not been updated for a long time and not showcase current recommended testing practices. We plan to update this document soon.

This section deals with integrating @testing-library/react-native with react-navigation, using Jest.

Stack Navigator​

Setting up​

Install the packages required for React Navigation. For this example, we will use a stack navigator to transition to the second page when any of the items are clicked on.

$ yarn add @react-native-community/masked-view @react-navigation/native @react-navigation/stack react-native-gesture-handler react-native-reanimated react-native-safe-area-context react-native-screens

Create an ./AppNavigator.js component which will list the navigation stack:

import 'react-native-gesture-handler';
import * as React from 'react';
import { createStackNavigator } from '@react-navigation/stack';

import HomeScreen from './screens/HomeScreen';
import DetailsScreen from './screens/DetailsScreen';

const { Screen, Navigator } = createStackNavigator();

export default function Navigation() {
const options = {};

return (
<Navigator>
<Screen name="Home" component={HomeScreen} />
<Screen options={options} name="Details" component={DetailsScreen} />
</Navigator>
);
}

Create your two screens which we will transition to and from them. The homescreen, found in ./screens/HomeScreen.js, contains a list of elements presented in a list view. On tap of any of these items will move to the details screen with the item number:

import * as React from 'react';
import { Text, View, FlatList, TouchableOpacity, StyleSheet } from 'react-native';

export default function HomeScreen({ navigation }) {
const [items] = React.useState(new Array(20).fill(null).map((_, idx) => idx + 1));

const onOpacityPress = (item) => navigation.navigate('Details', item);

return (
<View>
<Text style={styles.header}>List of numbers from 1 to 20</Text>
<FlatList
keyExtractor={(_, idx) => `${idx}`}
data={items}
renderItem={({ item }) => (
<TouchableOpacity onPress={() => onOpacityPress(item)} style={styles.row}>
<Text>Item number {item}</Text>
</TouchableOpacity>
)}
/>
</View>
);
}

const divider = '#DDDDDD';

const styles = StyleSheet.create({
header: {
fontSize: 20,
textAlign: 'center',
marginVertical: 16,
},
row: {
paddingVertical: 16,
paddingHorizontal: 24,
borderBottomColor: divider,
borderBottomWidth: 1,
},
});

The details screen, found in ./screens/DetailsScreen.js, contains a header with the item number passed from the home screen:

// ./screens/DetailsScreen.js
import * as React from 'react';
import { Text, StyleSheet, View } from 'react-native';

export default function DetailsScreen(props) {
const item = Number.parseInt(props.route.params, 10);

return (
<View>
<Text style={styles.header}>Showing details for {item}</Text>
<Text style={styles.body}>the number you have chosen is {item}</Text>
</View>
);
}

const styles = StyleSheet.create({
header: {
fontSize: 20,
textAlign: 'center',
marginVertical: 16,
},
body: {
textAlign: 'center',
},
});

Setting up the test environment​

Install required dev dependencies:

$ yarn add -D jest @testing-library/react-native

Create your jest.config.js file (or place the following properties in your package.json as a "jest" property)

module.exports = {
preset: 'react-native',
setupFiles: ['./node_modules/react-native-gesture-handler/jestSetup.js'],
transformIgnorePatterns: [
'node_modules/(?!(jest-)?@?react-native|@react-native-community|@react-navigation)',

// For pnpm you need to use inlcude `(?!(?:.pnpm/)?` part like this:
// 'node_modules/(?!(?:.pnpm/)?((jest-)?@?react-native|@react-native-community|@react-navigation))',
],
};

Notice the 2 entries that don't come with the default React Native project:

  • setupFiles – an array of files that Jest is going to execute before running your tests. In this case, we run react-native-gesture-handler/jestSetup.js which sets up necessary mocks for react-native-gesture-handler native module
  • transformIgnorePatterns – an array of paths that Jest ignores when transforming code. In this case, the negative lookahead regular expression is used, to tell Jest to transform (with Babel) every package inside node_modules/ that starts with react-native, @react-native-community or @react-navigation (added by us, the rest is in react-native preset by default, so you don't have to worry about it).

Example tests​

For this example, we are going to test out two things. The first thing is that the page is laid out as expected. The second, and most important, is that the page will transition to the detail screen when any item is tapped on.

Let's add a AppNavigator.test.js file in src/__tests__ directory:

import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { render, screen, fireEvent } from '@testing-library/react-native';

import AppNavigator from '../AppNavigator';

// Silence the warning https://github.com/facebook/react-native/issues/11094#issuecomment-263240420
// Use with React Native <= 0.63
jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper');

// Use this instead with React Native >= 0.64
// jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper');

describe('Testing react navigation', () => {
test('page contains the header and 10 items', async () => {
const component = (
<NavigationContainer>
<AppNavigator />
</NavigationContainer>
);

render(component);

const header = await screen.findByText('List of numbers from 1 to 20');
const items = await screen.findAllByText(/Item number/);

expect(header).toBeOnTheScreen();
expect(items.length).toBe(10);
});

test('clicking on one item takes you to the details screen', async () => {
const component = (
<NavigationContainer>
<AppNavigator />
</NavigationContainer>
);

render(component);
const toClick = await screen.findByText('Item number 5');

fireEvent(toClick, 'press');
const newHeader = await screen.findByText('Showing details for 5');
const newBody = await screen.findByText('the number you have chosen is 5');

expect(newHeader).toBeOnTheScreen();
expect(newBody).toBeOnTheScreen();
});
});

Drawer Navigator​

Testing the Drawer Navigation requires an additional setup step for mocking the Reanimated library.

Setting up​

Install the packages required for React Navigation. For this example, we will use a drawer navigator to transition between a home screen and an additional screen.

$ yarn add @react-native-community/masked-view @react-navigation/native @react-navigation/drawer react-native-gesture-handler react-native-reanimated react-native-safe-area-context react-native-screens

Create a ./DrawerAppNavigator.js component which will list the navigation stack:

import 'react-native-gesture-handler';
import React from 'react';
import { createDrawerNavigator } from '@react-navigation/drawer';

const { Screen, Navigator } = createDrawerNavigator();

export default function Navigation() {
return (
<Navigator>
<Screen name="Home" component={HomeScreen} />
<Screen name="Notifications" component={NotificationsScreen} />
</Navigator>
);
}

Create your two screens which we will transition to and from:

function HomeScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Welcome!</Text>
<Button onPress={() => navigation.navigate('Notifications')} title="Go to notifications" />
</View>
);
}

function NotificationsScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>This is the notifications screen</Text>
<Button onPress={() => navigation.goBack()} title="Go back home" />
</View>
);
}

Setting up the test environment​

Install required dev dependencies:

$ yarn add -D jest @testing-library/react-native

Create a mock file necessary for your tests:

import 'react-native-gesture-handler/jestSetup';

jest.mock('react-native-reanimated', () => {
const Reanimated = require('react-native-reanimated/mock');

// The mock for `call` immediately calls the callback which is incorrect
// So we override it with a no-op
Reanimated.default.call = () => {};

return Reanimated;
});

// Silence the warning: Animated: `useNativeDriver` is not supported because the native animated module is missing
jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper');

Create your jest.config.js file (or place the following properties in your package.json as a "jest" property)

module.exports = {
preset: 'react-native',
setupFilesAfterEnv: ['./jest-setup.js'],
transformIgnorePatterns: [
'node_modules/(?!(jest-)?react-native|@react-native-community|@react-navigation)',
],
};

Make sure that the path to the file in setupFiles is correct. Jest will run these files before running your tests, so it's the best place to put your global mocks.

This setup is copied from the React Navigation documentation.

Example tests​

For this example, we are going to test out two things. The first thing is that the screen is loaded correctly. The second, and most important, is that the page will transition to the notifications screen when the button is tapped on.

Let's add a DrawerAppNavigator.test.js file in src/__tests__ directory:

import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { render, screen, fireEvent } from '@testing-library/react-native';

import DrawerAppNavigator from '../DrawerAppNavigator';

describe('Testing react navigation', () => {
test('screen contains a button linking to the notifications page', async () => {
const component = (
<NavigationContainer>
<DrawerAppNavigator />
</NavigationContainer>
);

render(component);
const button = await screen.findByText('Go to notifications');

expect(button).toBeOnTheScreen();
});

test('clicking on the button takes you to the notifications screen', async () => {
const component = (
<NavigationContainer>
<DrawerAppNavigator />
</NavigationContainer>
);

render(component);
const oldScreen = screen.queryByText('Welcome!');
const button = await screen.findByText('Go to notifications');

expect(oldScreen).toBeOnTheScreen();

fireEvent(button, 'press');
const newScreen = await screen.findByText('This is the notifications screen');

expect(newScreen).toBeOnTheScreen();
});
});

Running tests​

To run the tests, place a test script inside your package.json

{
"scripts": {
"test": "jest"
}
}

And run the test script with npm test or yarn test.

+ \ No newline at end of file diff --git a/docs/redux-integration.html b/docs/redux-integration.html index e399b6c4..1cfd8c20 100644 --- a/docs/redux-integration.html +++ b/docs/redux-integration.html @@ -4,13 +4,13 @@ Redux Integration | React Native Testing Library - +

Redux Integration

caution

This examples has not been updated for a long time and not showcase current recommended testing practices. We plan to update this document soon.

This section deals with testing RN applications developed with Redux. We will be developing a simple TODO application capable of adding and removing an item. Once included, the timestamp is included.

Setting up​

An example of setting up can be found here.

Test cases​

Our test is on the components that either dispatch actions on the redux store or read some data from the redux store. This means we will test ./components/AddTodo.js and ./components/TodoList.js. Thus we will create ./components/AddTodo.test.js and ./components/TodoList.test.js

For ./components/AddTodo.test.js

import * as React from 'react';
import { Provider } from 'react-redux';
import { render, screen, fireEvent } from '@testing-library/react-native';
import configureStore from '../store';
import AddTodo from './AddTodo';

describe('AddTodo component test', () => {
test('adds a new TODO when the button is pressed', () => {
const store = configureStore();

const component = (
<Provider store={store}>
<AddTodo />
</Provider>
);

render(component);

// There is a TextInput.
// https://github.com/callstack/react-native-testing-library/blob/ae3d4af370487e1e8fedd8219f77225690aefc59/examples/redux/components/AddTodo.js#L24
const input = screen.getByPlaceholderText(/repository/i);
expect(input).toBeOnTheScreen();

const textToEnter = 'This is a random element';
fireEvent.changeText(input, textToEnter);
fireEvent.press(screen.getByText('Submit form'));

const todosState = store.getState().todos;

expect(todosState.length).toEqual(1);

expect(todosState).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: 1,
text: textToEnter,
date: expect.any(Date),
}),
])
);
});
});

For ./components/TodoList.test.js

import * as React from 'react';
import { Provider } from 'react-redux';
import { render, screen, fireEvent } from '@testing-library/react-native';
import configureStore from '../store';
import TodoList from './TodoList';

describe('TodoList component test', () => {
test('it should execute with a store with 4 elements', () => {
const initialState = {
todos: [
{ id: 1, text: 'Sing something', date: new Date() },
{ id: 2, text: 'Dance something', date: new Date() },
{ id: 3, text: 'Sleep something', date: new Date() },
{ id: 4, text: 'Sleep something', date: new Date() },
],
};
const store = configureStore(initialState);

const component = (
<Provider store={store}>
<TodoList />
</Provider>
);

render(component);
const todoElems = screen.getAllByText(/something/i);

expect(todoElems.length).toEqual(4);
});

test('should execute with 2 elements and end up with 1 after delete', () => {
const initialState = {
todos: [
{ id: 1, text: 'Sing something', date: new Date() },
{ id: 2, text: 'Dance something', date: new Date() },
],
};
const store = configureStore(initialState);

const component = (
<Provider store={store}>
<TodoList />
</Provider>
);

render(component);
const todoElems = screen.getAllByText(/something/i);

expect(todoElems.length).toBe(2);

const buttons = screen.getAllByText('Delete');
expect(buttons.length).toBe(2);

fireEvent.press(buttons[0]);
expect(screen.getAllByText('Delete').length).toBe(1);
});
});

Running tests​

To run the tests, place a test script inside your package.json

{
"scripts": {
"test": "jest"
}
}

And run the test script with npm test or yarn test.

- + \ No newline at end of file diff --git a/docs/testing-env.html b/docs/testing-env.html index 42f80094..324930ec 100644 --- a/docs/testing-env.html +++ b/docs/testing-env.html @@ -4,13 +4,13 @@ Testing Environment | React Native Testing Library - +

Testing Environment

info

This document is intended for a more advanced audience who want to understand the internals of our testing environment better, e.g., to contribute to the codebase. You should be able to write integration or component tests without reading this.

React Native Testing Library allows you to write integration and component tests for your React Native app or library. While the JSX code used in tests closely resembles your React Native app, things are not as simple as they might appear. This document will describe the key elements of our testing environment and highlight things to be aware of when writing more advanced tests or diagnosing issues.

React renderers​

React allows you to write declarative code using JSX, write function or class components, or use hooks like useState. You need to use a renderer to output the results of your components. Every React app uses some renderer:

  • React Native is a renderer for mobile apps,
  • React DOM is a renderer for web apps,
  • There are other more specialized renderers that can e.g., render to console or HTML canvas.

When you run your tests in the React Native Testing Library, somewhat contrary to what the name suggests, they are actually not using React Native renderer. This is because this renderer needs to be run on an iOS or Android operating system, so it would need to run on a device or simulator.

React Test Renderer​

Instead, RNTL uses React Test Renderer, a specialized renderer that allows rendering to pure JavaScript objects without access to mobile OS and can run in a Node.js environment using Jest (or any other JavaScript test runner).

Using React Test Renderer has pros and cons.

Benefits:

  • tests can run on most CIs (Linux, etc) and do not require a mobile device or emulator
  • faster test execution
  • light runtime environment

Disadvantages:

  • Tests do not execute native code
  • Tests are unaware of the view state that would be managed by native components, e.g., focus, unmanaged text boxes, etc.
  • Assertions do not operate on native view hierarchy
  • Runtime behaviors are simulated, sometimes imperfectly

It's worth noting that the React Testing Library (web one) works a bit differently. While RTL also runs in Jest, it has access to a simulated browser DOM environment from the jsdom package, which allows it to use a regular React DOM renderer. Unfortunately, there is no similar React Native runtime environment package. This is probably because while the browser environment is well-defined and highly standardized, the React Native environment constantly evolves in sync with the evolution of underlying OS-es. Maintaining such an environment would require duplicating countless React Native behaviors and keeping them in sync as React Native develops.

Element tree​

Calling the render() function creates an element tree. This is done internally by invoking TestRenderer.create() method. The output tree represents your React Native component tree, and each node of that tree is an "instance" of some React component (to be more precise, each node represents a React fiber, and only class components have instances, while function components store the hook state using fibers).

These tree elements are represented by ReactTestInstance type:

interface ReactTestInstance {
type: ElementType;
props: { [propName: string]: any };
parent: ReactTestInstance | null;
children: Array<ReactTestInstance | string>;

// Other props and methods
}

Based on: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react-test-renderer/index.d.ts

Host and composite components​

One of the most important aspects of the element tree is that it is composed of both host and composite components:

  • Host components will have direct counterparts in the native view tree. Typical examples are <View>, <Text> , <TextInput>, and <Image> from React Native. You can think of these as an analog of <div>, <span> etc on the Web. You can also create custom host views as native modules or import them from 3rd party libraries, like React Navigation or React Native Gesture Handler.
  • Composite components are React code organization units that exist only on the JavaScript side of your app. Typical examples are components you create (function and class components), components imported from React Native (View, Text, etc.), or 3rd party packages.

That might initially sound confusing since we put React Native's View in both categories. There are two View components: composite and host. The relation between them is as follows:

  • composite View is the type imported from the react-native package. It is a JavaScript component that renders the host View as its only child in the element tree.
  • host View, which you do not render directly. React Native takes the props you pass to the composite View, does some processing on them and passes them to the host View.

The part of the tree looks as follows:

* <View> (composite)
* <View> (host)
* children prop passed in JSX

A similar relation exists between other composite and host pairs: e.g. Text , TextInput, and Image components:

* <Text> (composite)
* <Text> (host)
* string (or mixed) content

Not all React Native components are organized this way, e.g., when you use Pressable (or TouchableOpacity), there is no host Pressable, but composite Pressable is rendering a host View with specific props being set:

* <Pressable> (composite)
* <View accessible={true} {...}> (host)
* children prop passed in JSX

Differentiating between host and composite elements​

Any easy way to differentiate between host and composite elements is the type prop of ReactTestInstance:

  • for host components, it's always a string value representing a component name, e.g., "View"
  • for composite components, it's a function or class corresponding to the component

You can use the following code to check if a given element is a host one:

function isHostElement(element: ReactTestInstance) {
return typeof element.type === 'string';
}

Tree nodes​

We encourage you to only assert values on host views in your tests because they represent the user interface view and controls which the user can see and interact with. Users cannot see or interact with composite views as they exist purely in the JavaScript domain and do not generate any visible UI.

Asserting props​

For example, suppose you assert a style prop of a composite element. In that case, there is no guarantee that the style will be visible to the user, as the component author can forget to pass this prop to some underlying View or other host component. Similarly onPress event handler on a composite prop can be unreachable by the user.

function ForgotToPassPropsButton({ title, onPress, style }) {
return (
<Pressable>
<Text>{title}</Text>
</Pressable>
);
}

In the above example, user-defined components accept both onPress and style props but do not pass them (through Pressable) to host views, so they will not affect the user interface. Additionally, React Native and other libraries might pass some of the props under different names or transform their values between composite and host components.

Tree navigation​

caution

You should avoid navigating over the element tree, as this makes your testing code fragile and may result in false positives. This section is more relevant for people who want to contribute to our codebase.

You will encounter host and composite elements when navigating a tree of react elements using parent or children props of a ReactTestInstance element. You should be careful when navigating the element tree, as the tree structure for third-party components can change independently from your code and cause unexpected test failures.

Inside RNTL, we have various tree navigation helpers: getHostParent, getHostChildren, etc. These are intentionally not exported, as using them is not recommended.

Queries​

All recommended Testing Library queries return host components to encourage the best practices described above.

Only UNSAFE_*ByType and UNSAFE_*ByProps queries can return both host and composite components depending on used predicates. They are marked as unsafe precisely because testing composite components makes your test more fragile.

- + \ No newline at end of file diff --git a/docs/troubleshooting.html b/docs/troubleshooting.html index 60b45daa..f7c3668c 100644 --- a/docs/troubleshooting.html +++ b/docs/troubleshooting.html @@ -4,13 +4,13 @@ Troubleshooting | React Native Testing Library - +
-

Troubleshooting

This guide describes common issues found by users when integrating React Native Test Library to their projects:

## Matching React Native, React & React Test Renderer versions

Check that you have matching versions of core dependencies:

  • React Native
  • React
  • React Test Renderer

React Native uses different versioning scheme from React, you can use React Native Upgrade Helper to find the correct matching between React Native & React versions. In case you use Expo, you should use dependency versions recommended by them and set by expo upgrade command.

React Test Renderer usually has same major & minor version as React, as they are closely related and React Test Renderer is part of React monorepo.

Related issues: #1061, #938, #920

Errors that might indicate that you are facing this issue:

  • TypeError: Cannot read property 'current' of undefined when calling render()
  • TypeError: Cannot read property 'isBatchingLegacy' of undefined when calling render()

Example repository​

We maintain an example repository that showcases a modern React Native Testing Library setup with TypeScript, etc.

In case something does not work in your setup you can refer to this repository for recommended configuration.

Undefined component error​

Warning: React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined.

This frequently happens when you mock a complex module incorrectly, e.g.:

jest.mock('@react-navigation/native', () => {
return {
useNavigation: jest.fn(),
};
})

The above mock will mock useNavigation hook as intended, but at the same time all other exports from @react-navigation/native package are now undefined. If you want to use NavigationContainer component from the same package it will be undefined and result in the error above.

In order to mock only a part of given package you should re-export all other exports using jest.requireActual helper:

jest.mock('@react-navigation/native', () => {
return {
...jest.requireActual('@react-navigation/native'),
useNavigation: jest.fn(),
};
})

That way the mock will re-export all of the @react-navigation/native members and overwrite only the useNavigation hook.

Alternatively, you can use jest.spyOn to mock package exports selectively.

Mocking React Native​

In case of mocking react-native package you should not mock the whole package at once, as this approach has issues with jest.requireActual call. In this case it is recommended to mock particular library paths inside the package, e.g.:

jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter');

Act warnings​

When writing tests you may encounter warnings connected with act() function. There are two kinds of these warnings:

  • sync act() warning - Warning: An update to Component inside a test was not wrapped in act(...)
  • async act() warning - Warning: You called act(async () => ...) without await

You can read more about act() function in our understanding act function guide.

Normally, you should not encounter sync act() warnings, but if that happens this probably indicate an issue with your test and should be investigated.

In case of async act() function this might happen more or less randomly, especially if your components contain async logic. So far this warning does not seem to affect test correctness.

- +

Troubleshooting

This guide describes common issues found by users when integrating React Native Test Library to their projects:

## Matching React Native, React & React Test Renderer versions

Check that you have matching versions of core dependencies:

  • React Native
  • React
  • React Test Renderer

React Native uses different versioning scheme from React, you can use React Native Upgrade Helper to find the correct matching between React Native & React versions. In case you use Expo, you should use dependency versions recommended by them and set by expo upgrade command.

React Test Renderer usually has same major & minor version as React, as they are closely related and React Test Renderer is part of React monorepo.

Related issues: #1061, #938, #920

Errors that might indicate that you are facing this issue:

  • TypeError: Cannot read property 'current' of undefined when calling render()
  • TypeError: Cannot read property 'isBatchingLegacy' of undefined when calling render()

Example repository​

We maintain an example repository that showcases a modern React Native Testing Library setup with TypeScript, etc.

In case something does not work in your setup you can refer to this repository for recommended configuration.

Undefined component error​

Warning: React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined.

This frequently happens when you mock a complex module incorrectly, e.g.:

jest.mock('@react-navigation/native', () => {
return {
useNavigation: jest.fn(),
};
});

The above mock will mock useNavigation hook as intended, but at the same time all other exports from @react-navigation/native package are now undefined. If you want to use NavigationContainer component from the same package it will be undefined and result in the error above.

In order to mock only a part of given package you should re-export all other exports using jest.requireActual helper:

jest.mock('@react-navigation/native', () => {
return {
...jest.requireActual('@react-navigation/native'),
useNavigation: jest.fn(),
};
});

That way the mock will re-export all of the @react-navigation/native members and overwrite only the useNavigation hook.

Alternatively, you can use jest.spyOn to mock package exports selectively.

Mocking React Native​

In case of mocking react-native package you should not mock the whole package at once, as this approach has issues with jest.requireActual call. In this case it is recommended to mock particular library paths inside the package, e.g.:

jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter');

Act warnings​

When writing tests you may encounter warnings connected with act() function. There are two kinds of these warnings:

  • sync act() warning - Warning: An update to Component inside a test was not wrapped in act(...)
  • async act() warning - Warning: You called act(async () => ...) without await

You can read more about act() function in our understanding act function guide.

Normally, you should not encounter sync act() warnings, but if that happens this probably indicate an issue with your test and should be investigated.

In case of async act() function this might happen more or less randomly, especially if your components contain async logic. So far this warning does not seem to affect test correctness.

+ \ No newline at end of file diff --git a/docs/understanding-act.html b/docs/understanding-act.html index a07f5817..3431e9fd 100644 --- a/docs/understanding-act.html +++ b/docs/understanding-act.html @@ -4,13 +4,13 @@ Understanding Act function | React Native Testing Library - +

Understanding Act function

When writing RNTL tests one of the things that confuses developers the most are cryptic act() function errors logged into console. In this article I will try to build an understanding of the purpose and behaviour of act() so you can build your tests with more confidence.

act warnings​

Let’s start with typical act() warnings logged to console. There are two kinds of these issues, let’s call the first one the "sync act()" warning:

Warning: An update to Component inside a test was not wrapped in act(...).

When testing, code that causes React state updates should be wrapped into act(...):

act(() => {
/* fire events that update state */
});
/* assert on the output */

The second one relates to async usage of act so let’s call it the "async act" error:

Warning: You called act(async () => ...) without await. This could lead to unexpected
testing behaviour, interleaving multiple act calls and mixing their scopes. You should
- await act(async () => ...);

Synchronous act​

Responsibility​

This function is intended only for using in automated tests and works only in development mode. Attempting to use it in production build will throw an error.

The responsibility for act function is to make React renders and updates work in tests in a similar way they work in real application by grouping and executing related units of interaction (e.g. renders, effects, etc) together.

To showcase that behaviour let make a small experiment. First we define a function component that uses useEffect hook in a trivial way.

function TestComponent() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
setCount((c) => c + 1);
}, []);

return <Text>Count {count}</Text>;
}

In the following tests we will directly use ReactTestRenderer instead of RNTL render function to render our component for tests. In order to expose familiar queries like getByText we will use within function from RNTL.

test('render without act', () => {
const renderer = TestRenderer.create(<TestComponent />);

// Bind RNTL queries for root element.
const view = within(renderer.root);
expect(view.getByText('Count 0')).toBeOnTheScreen();
});

When testing without act call wrapping rendering call, we see that the assertion runs just after the rendering but before useEffecthooks effects are applied. Which is not what we expected in our tests.

test('render with act', () => {
let renderer: ReactTestRenderer;
act(() => {
renderer = TestRenderer.create(<TestComponent />);
});

// Bind RNTL queries for root element.
const view = within(renderer!.root);
expect(view.getByText('Count 1')).toBeOnTheScreen();
});

When wrapping rendering call with act we see that the changes caused by useEffect hook have been applied as we would expect.

When to use act​

The name act comes from Arrange-Act-Assert unit testing pattern. Which means it’s related to part of the test when we execute some actions on the component tree.

So far we learned that act function allows tests to wait for all pending React interactions to be applied before we make our assertions. When using act we get guarantee that any state updates will be executed as well as any enqueued effects will be executed.

Therefore, we should use act whenever there is some action that causes element tree to render, particularly:

  • initial render call - ReactTestRenderer.create call
  • re-rendering of component -renderer.update call
  • triggering any event handlers that cause component tree render

Thankfully, for these basic cases RNTL has got you covered as our render, update and fireEvent methods already wrap their calls in sync act so that you do not have to do it explicitly.

Note that act calls can be safely nested and internally form a stack of calls. However, overlapping act calls, which can be achieved using async version of act, are not supported.

Implementation​

As of React version of 18.1.0, the act implementation is defined in the ReactAct.js source file inside React repository. This implementation has been fairly stable since React 17.0.

RNTL exports act for convenience of the users as defined in the act.ts source file. That file refers to ReactTestRenderer.js source file from React Test Renderer package, which finally leads to React act implementation in ReactAct.js (already mentioned above).

Asynchronous act​

So far we have seen synchronous version of act which runs its callback immediately. This can deal with things like synchronous effects or mocks using already resolved promises. However, not all component code is synchronous. Frequently our components or mocks contain some asynchronous behaviours like setTimeout calls or network calls. Starting from React 16.9, act can also be called in asynchronous mode. In such case act implementation checks that the passed callback returns object resembling promise.

Asynchronous code​

Asynchronous version of act also is executed immediately, but the callback is not yet completed because of some asynchronous operations inside.

Lets look at a simple example with component using setTimeout call to simulate asynchronous behaviour:

function TestAsyncComponent() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
setTimeout(() => {
setCount((c) => c + 1);
}, 50);
}, []);

return <Text>Count {count}</Text>;
}
test('render async natively', () => {
const view = render(<TestAsyncComponent />);
expect(view.getByText('Count 0')).toBeOnTheScreen();
});

If we test our component in a native way without handling its asynchronous behaviour we will end up with sync act warning:

Warning: An update to TestAsyncComponent inside a test was not wrapped in act(...).

When testing, code that causes React state updates should be wrapped into act(...):

act(() => {
/* fire events that update state */
});
/* assert on the output */

Note that this is not yet the infamous async act warning. It only asks us to wrap our event code with act calls. However, this time our immediate state change does not originate from externally triggered events but rather forms an internal part of the component. So how can we apply act in such scenario?

Solution with fake timers​

First solution is to use Jest's fake timers inside out tests:

test('render with fake timers', () => {
jest.useFakeTimers();
const view = render(<TestAsyncComponent />);

act(() => {
jest.runAllTimers();
});
expect(view.getByText('Count 1')).toBeOnTheScreen();
});

That way we can wrap jest.runAllTimers() call which triggers the setTimeout updates inside an act call, hence resolving the act warning. Note that this whole code is synchronous thanks to usage of Jest fake timers.

Solution with real timers​

If we wanted to stick with real timers then things get a bit more complex. Let’s start by applying a crude solution of opening async act() call for the expected duration of components updates:

test('render with real timers - sleep', async () => {
const view = render(<TestAsyncComponent />);
await act(async () => {
await sleep(100); // Wait a bit longer than setTimeout in `TestAsyncComponent`
});

expect(view.getByText('Count 1')).toBeOnTheScreen();
});

This works correctly as we use an explicit async act() call that resolves the console error. However, it relies on our knowledge of exact implementation details which is a bad practice.

Let’s try more elegant solution using waitFor that will wait for our desired state:

test('render with real timers - waitFor', async () => {
const view = render(<TestAsyncComponent />);

await waitFor(() => view.getByText('Count 1'));
expect(view.getByText('Count 1')).toBeOnTheScreen();
});

This also works correctly, because waitFor call executes async act() call internally.

The above code can be simplified using findBy query:

test('render with real timers - findBy', async () => {
const view = render(<TestAsyncComponent />);

expect(await view.findByText('Count 1')).toBeOnTheScreen();
});

This also works since findByText internally calls waitFor which uses async act().

Note that all of the above examples are async tests using & awaiting async act() function call.

Async act warning​

If we modify any of the above async tests and remove await keyword, then we will trigger the notorious async act()warning:

Warning: You called act(async () => ...) without await. This could lead to unexpected
testing behaviour, interleaving multiple act calls and mixing their scopes. You should
- await act(async () => ...);

React decides to show this error whenever it detects that async act()call has not been awaited.

The exact reasons why you might see async act() warnings vary, but finally it means that act() has been called with callback that returns Promise-like object, but it has not been waited on.

References​

- + \ No newline at end of file diff --git a/docs/user-event.html b/docs/user-event.html index f5c84d73..13133298 100644 --- a/docs/user-event.html +++ b/docs/user-event.html @@ -4,13 +4,13 @@ User Event | React Native Testing Library - +

User Event

note

User Event interactions require RNTL v12.2.0 or later.

Comparison with Fire Event API​

Fire Event is our original event simulation API. It can invoke any event handler declared on either host or composite elements. Suppose the element does not have onEventName event handler for the passed eventName event, or the element is disabled. In that case, Fire Event will traverse up the component tree, looking for an event handler on both host and composite elements along the way. By default, it will not pass any event data, but the user might provide it in the last argument.

In contrast, User Event provides realistic event simulation for user interactions like press or type. Each interaction will trigger a sequence of events corresponding to React Native runtime behavior. These events will be invoked only on host elements, and will automatically receive event data corresponding to each event.

If User Event supports a given interaction, you should always prefer it over the Fire Event counterpart, as it will make your tests much more realistic and, hence, reliable. In other cases, e.g., when User Event does not support the given event or when invoking event handlers on composite elements, you have to use Fire Event as the only available option.

setup()​

userEvent.setup(options?: {
delay: number;
advanceTimers: (delay: number) => Promise<void> | void;
})

Example

const user = userEvent.setup();

Creates a User Event object instance, which can be used to trigger events.

Options​

  • delay controls the default delay between subsequent events, e.g., keystrokes.
  • advanceTimers is a time advancement utility function that should be used for fake timers. The default setup handles both real timers and Jest fake timers.

press()​

press(
element: ReactTestInstance,
): Promise<void>

Example

const user = userEvent.setup();
await user.press(element);

This helper simulates a press on any pressable element, e.g. Pressable, TouchableOpacity, Text, TextInput, etc. Unlike fireEvent.press(), a more straightforward API that will only call the onPress prop, this function simulates the entire press interaction in a more realistic way by reproducing the event sequence emitted by React Native runtime. This helper will trigger additional events like pressIn and pressOut.

longPress()​

longPress(
element: ReactTestInstance,
options: { duration: number } = { duration: 500 }
): Promise<void>

Example

const user = userEvent.setup();
await user.longPress(element);

Simulates a long press user interaction. In React Native, the longPress event is emitted when the press duration exceeds the long press threshold (by default, 500 ms). In other aspects, this action behaves similarly to regular press action, e.g., by emitting pressIn and pressOut events. The press duration is customizable through the options. This should be useful if you use the delayLongPress prop. When using real timers, this will take 500 ms, so it is highly recommended to use that API with fake timers to prevent the test from taking a long time to run.

Options​

  • duration - duration of the press in milliseconds. The default value is 500 ms.

type()​

type(
element: ReactTestInstance,
text: string,
options?: {
skipPress?: boolean
submitEditing?: boolean
}

Example

const user = userEvent.setup();
await user.type(textInput, 'Hello world!');

This helper simulates the user focusing on a TextInput element, typing text one character at a time, and leaving the element.

This function supports only host TextInput elements. Passing other element types will result in throwing an error.

note

This function will add text to the text already present in the text input (as specified by value or defaultValue props). To replace existing text, use clear() helper first.

Options​

  • skipPress - if true, pressIn and pressOut events will not be triggered.
  • submitEditing - if true, submitEditing event will be triggered after typing the text.

Sequence of events​

The sequence of events depends on the multiline prop and the passed options.

Events will not be emitted if the editable prop is set to false.

Entering the element:

  • pressIn (optional)
  • focus
  • pressOut (optional)

The pressIn and pressOut events are sent by default but can be skipped by passing the skipPress: true option.

Typing (for each character):

  • keyPress
  • textInput (optional)
  • change
  • changeText
  • selectionChange

The textInput event is sent only for multiline text inputs.

Leaving the element:

  • submitEditing (optional)
  • endEditing
  • blur

The submitEditing event is skipped by default. It can sent by setting the submitEditing: true option.

clear()​

clear(
element: ReactTestInstance,
}

Example

const user = userEvent.setup();
await user.clear(textInput);

This helper simulates the user clearing the content of a TextInput element.

This function supports only host TextInput elements. Passing other element types will result in throwing an error.

Sequence of events​

The sequence of events depends on the multiline prop and passed options.

Events will not be emitted if the editable prop is set to false.

Entering the element:

  • focus

Selecting all content:

  • selectionChange

Pressing backspace:

  • keyPress
  • textInput (optional)
  • change
  • changeText
  • selectionChange

The textInput event is sent only for multiline text inputs.

Leaving the element:

  • endEditing
  • blur

scrollTo()​

note

scrollTo interaction has been introduced in RNTL v12.4.0.

scrollTo(
element: ReactTestInstance,
options: {
y: number,
momentumY?: number,
contentSize?: { width: number, height: number },
layoutMeasurement?: { width: number, height: number },
} | {
x: number,
momentumX?: number,
contentSize?: { width: number, height: number },
layoutMeasurement?: { width: number, height: number },
}

Example

const user = userEvent.setup();
await user.scrollTo(scrollView, { y: 100, momentumY: 200 });

This helper simulates the user scrolling a host ScrollView element.

This function supports only host ScrollView elements, passing other element types will result in an error. Note that FlatList is accepted as it renders to a host ScrolLView element.

Scroll interaction should match the ScrollView element direction:

  • for a vertical scroll view (default or horizontal={false}), you should pass only the y option (and optionally also momentumY).
  • for a horizontal scroll view (horizontal={true}), you should pass only the x option (and optionally momentumX).

Each scroll interaction consists of a mandatory drag scroll part, which simulates the user dragging the scroll view with his finger (the y or x option). This may optionally be followed by a momentum scroll movement, which simulates the inertial movement of scroll view content after the user lifts his finger (momentumY or momentumX options).

Options​

  • y - target vertical drag scroll position
  • x - target horizontal drag scroll position
  • momentumY - target vertical momentum scroll position
  • momentumX - target horizontal momentum scroll position
  • contentSize - passed to ScrollView events and enabling FlatList updates
  • layoutMeasurement - passed to ScrollView events and enabling FlatList updates

User Event will generate several intermediate scroll steps to simulate user scroll interaction. You should not rely on exact number or values of these scrolls steps as they might be change in the future version.

This function will remember where the last scroll ended, so subsequent scroll interaction will starts from that position. The initial scroll position will be assumed to be { y: 0, x: 0 }.

To simulate a FlatList (and other controls based on VirtualizedList) scrolling, you should pass the contentSize and layoutMeasurement options, which enable the underlying logic to update the currently visible window.

Sequence of events​

The sequence of events depends on whether the scroll includes an optional momentum scroll component.

Drag scroll:

  • contentSizeChange
  • scrollBeginDrag
  • scroll (multiple events)
  • scrollEndDrag

Momentum scroll (optional):

  • momentumScrollBegin
  • scroll (multiple events)
  • momentumScrollEnd
- + \ No newline at end of file diff --git a/index.html b/index.html index a55eb4cc..f74d8d74 100644 --- a/index.html +++ b/index.html @@ -4,13 +4,13 @@ React Native Testing Library | React Native Testing Library - +

React Native Testing Library

Helps you to write better tests with less effort.

Get Started
Maintainable

Maintainable

Write maintainable tests for your React Native apps

Reliable

Reliable

Promotes testing public APIs and avoiding implementation details

Community Driven

Community Driven

Supported by React Native community and its core contributors

Like the project? ⚛️ Join the team who does amazing stuff for clients and drives React Native Open Source! 🔥
- + \ No newline at end of file diff --git a/search.html b/search.html index cc416876..082fc989 100644 --- a/search.html +++ b/search.html @@ -4,13 +4,13 @@ Search the documentation | React Native Testing Library - +

Search the documentation

- + \ No newline at end of file