May 2024
so this month, taking the advice of one of the smartest people i know, I'll be taking up or rather, ill be trying to get in-depth understanding of React and its testing library.
so, for this, we're going to come across a vite app shows 6 random products as soon as the app loads, now every product has a thumbnail, a category, a title, a price and a "add to cart" button if the user scrolls down, theres a "Load More Products" button that gives the user 6 more products.
so what sort of tests can we write for this project? for this lets understand the processes that occur app loads -> 6 products are shown -> press load more button -> 6 more products are shown
so if you think about it, we can write 2 tests in this scenario one test to check if 6 products are shown as soon as the app launches and one more test to check if 6 more products are shown when we click on the button
so in our project we create a file called App.test.js
import { render, screen, waitFor } from "@testing-library/react";
import user from "@testing-library/user-event";
import App from "./App";
test("shows 6 products by default", async () => {
// going to write a fn thats going to render our Apo component
render(<App />);
const titles = await screen.findAllByRole("heading");
expect(titles).toHaveLength(6);
});
test("Clicking on the button shows 6 more products", async () => {
render(<App />);
const button = await screen.findByRole("button", {
name: /load more/i,
});
user.click(button);
await waitFor(async () => {
const titles = await screen.findAllByRole("heading");
expect(titles).toHaveLength(12);
});
});
lets start off with a list of packages that we'll end up using when we talk about testing a react application.
@testing-library/react- takes our component and renders it, and gets it ready for testing.@testing-library/user-event- helps simulate user input like typing and clicking. for this, BTS we're using another lib called@testing-library/dom. now, this package is already included in our proj with@testing-library/reactand this/dompackage helps us find eles that are rendered by our componentsjest- runs our tests, reports resultsjsdom- simulates a browser when running in a node env
now, when we just ran yarn test how is it that all the tests were run automatically?
so
jestfinds all files in the src folder that end with.spec.js, or.test.jsor are placed in a folder called__test__
test writing process for the first project
- pick out one component to test in isolation
- make a test file for the component
- decide what the imp parts of the component are
- write a test to make sure all the parts of the components work as expected
- run test at cmdline
WRT UserForm component
what are the most imp parts of this component?
- show 2 inputs and 1 button
- enter a name + email and then submitting the form calls the onUserAdd callback to be called
import { render, screen } from "@testing-library/react";
import user from "@testing-library/user-event";
import UserForm from "./UserForm";
test("check for 2 inputs and a button", () => {
// render the component
render(<UserForm />);
// check if the inputs and the button are being rendered
const inputs = screen.getAllByRole("textbox");
const button = screen.getByRole("button");
// assertion: to make sure that the component is behaving as expected
expect(inputs).toHaveLength(2);
expect(button).toBeInTheDocument();
});
queries
- a super imp part of testing is finding the ele that our component has created.
- query fns are how we are going to find eles that our component has rendered
- tedious to find eles
- there are about 48 fns that are used to find eles
- these fns are provided by the RTL Query System
- some ones that are most probably used (no need to memorise)
- screen.getByRole()
- screen.findAllByDisplayValue()
- screen.queryAllByRole()
- screen.queryByRole()
- screen.findByRole()
- screen.queryByLabelText()
- screen.findAllByTitle()
- screen.findByTitle()
- screen.getByLabelText()
understanding the queries we wrote earlier in the UserForm test
const inputs = screen.getAllByRole("textbox");
const button = screen.getByRole("button");
- the role is referring to something known as an
ARIA role - Aria roles clarify the purpose of a HTML ele
- mostly used by screenreaders
- many html eles have an 'implicit', or automatically assigned role
- eles can be assigned a role manually also and even trained engineers do this incorrectly at times (not advised to do)
now, some roles are:
- 'heading' -> h1 h2 h3 h4 h5 h6
- 'list' -> ul li
- 'button' -> button
- 'link' -> a
- 'textbox' -> input, type="text"
- ... and much more
finding eles by this 'role' method is the preferred way of testing, and RTL pushes us to use this method
assertions
basically telling the test file the expected behaviour
so to test this assertion we use the expect keyword which is a global keyword
and this expect always takes in a value and to this value we attach a fn called as a matcher, ie something like .toHaveLength(2) etc
there are MANY matchers, some provided by jest, some provided by RTL
now, WRT the UserForm component, we're getting the onUserAdd prop from the parent App, but when we run this test file as is, notice that we aren't passing a prop so then technically, onUserAdd is undefined, and this is why the test would fail
so what we can do is, we can pass the onUserAdd prop and give it an empty fn
<UserForm onUserAdd={() => {}} />
now there is a small problem, now the goal of this test was to make sure that the test is called with the email and name
so we need to write a function to verify that its being called and also make sure that its being called with the right args
so there is a way to do this NOTE: THIS IS NOT THE BEST IMPLEMENTATION BUT AN IMPLEMENTATION NEVERTHELESS
we can have an array called argList that stores all the arguments and also a function to store all these args to the array
const argList = [];
const callback = (...args) => {
argList.push(args);
};
and we pass this callback to the component
so this is the test so far
test("should call onUserAdd when the form is submitted", async () => {
// NOT THE BEST IMPLEMENTATION
const argList = [];
const callback = (...args) => {
argList.push(args);
};
// try to render the component
render(<UserForm onUserAdd={callback} />);
// find the 2 inputs
const [nameInput, emailInput] = screen.getAllByRole("textbox");
// simulate typing in a name
await user.click(nameInput);
await user.keyboard("test name");
// simulate typing in a email
await user.click(emailInput);
await user.keyboard("test@test.com");
// find the button
const button = screen.getByRole("button");
// simulate clicking the button
await user.click(button);
// assertion to make sure onUserAdd is called with email and name
expect(argList).toHaveLength(1);
expect(argList[0][0]).toEqual({ name: "test name", email: "test@test.com" });
});
mock fns
- its a fake fn that doesnt do anything when its called
- it gets recorded whenever we call it and the args it was called with
- used very often when we need to make sure a component calls a callback fn
- so the mock fn we write is going to store 2 things: how many times it was called, and the args it recvd
- so then we can make assertions to make sure that our mock fn was called once and that were getting the correct data also
- how do we define a mock fn?
const mock = jest.fn()
and jest has some inbuilt matchers to make sure a mock fn gets called and to make sure it gets the appr. props
// makes sure the mock was called
expect(mock).toHaveBeenCalled();
// what args it shouldve received
expect(mock).toHaveBeenCalledWith({
name: "test name",
email: "test@test.com",
});
another issue we'll have in time, in our UserForm component currently we've typed just 2 input fields name and email in that order, but what if tomo we reearrange it and add more fields? then this line const [nameInput, emailInput] = screen.getAllByRole("textbox"); would fail the test as it is brittle and hardcoded.
so how do we fix this?
for labels we can provide a htmlFor attr. such that when we tap/click on this label, the input field is active
<label htmlFor="email">enter email</label>
<input type="email" id="email" placeholder="email" />
so one way we can target this element can be screen.getByLabelText(/enter email/i) or screen.getByRole('textbox', {name: /enter email/i })
RTL recommends we use roles, so thats what well be using the 'i' in the end is basically telling to ignore case sensitivity
so now we have a flexible test, such that if we reorder the components, add more components, this test now, wont fail and this is the code so far
test("should call onUserAdd when the form is submitted", async () => {
const mock = jest.fn();
// try to render the component
render(<UserForm onUserAdd={mock} />);
// find the 2 inputs
const nameInput = screen.getByRole("textbox", { name: /name/i });
const emailInput = screen.getByRole("textbox", { name: /email/i });
// simulate typing in a name
await user.click(nameInput);
await user.keyboard("test name");
// simulate typing in a email
await user.click(emailInput);
await user.keyboard("test@test.com");
// find the button
const button = screen.getByRole("button");
// simulate clicking the button
await user.click(button);
// assertion to make sure onUserAdd is called with email and name
expect(mock).toHaveBeenCalled();
expect(mock).toHaveBeenCalledWith({
name: "test name",
email: "test@test.com",
});
});
what to do if you dont know which query to use
so RTL provides a fn screen.logTestingPlaygroundURL() which is a playground that lets us find the query to help us with the task at hand
and what this'll do is, itll spin up a playground of the component we're trying to test and it'll help us by suggesting possible queries we can use
now at times, we may not be able to select a particular element, which is when we can try styling it, to see if that works and 9/10 times we'll most probably go for the suggested query
sometimes finding eles by role just doesnt work well dont obsess over getting the "right" query
here are 2 "escape hatch" ways to find eles when the preferred role approach doesnt work
- fallback #1 - to find eles using an attr called
data-testid- now if we want to check the # of rows within tbody, we can go to the UserList component, and give the tbody this attr
data-testid="users" - what does this mean? by giving the above prop, we're gaining the ability to target this ele and find whats within it using
{within}from@testing-library/reactimport { render, screen, within } from "@testing-library/react"; const rows = within(screen.getByTestId("users")).getAllByRole("row");
- now if we want to check the # of rows within tbody, we can go to the UserList component, and give the tbody this attr
- but this is not advised as we literally have to change the codebase JUST so that we can test it
- fallback #2 - container.querySelector()
- so when we call the render fn we're returned multiple things and we can destructure that and take the
{container}property - now, if we go back to our playground you'll see that the entire component is wrapped up inside a
<div>now this div is our container - and we can perform our normal querySelectors on this container
- but when we run
const table = container.querySelector("table");we get a red underline in vscode. this is not an error but just a warning asking to avoid this approach and use roles instead - and we can simply run the following query to check
// trying to target tr elements inside tbody
const rows = container.querySelectorAll("tbody tr");
expect(rows).toHaveLength(users.length);
});
and this would still have the curly underline as it still recommends us to use roles, but if you want to bypass this, have a comment above the underlined line and type the following // eslint-disable-next-line
- so the whole point is, dont spend too much time trying to find roles to use. spend at most 5-6mins to find a role and then test it
testing the userlist component
we can run the playground tool to find out which role to choose, and if we click on the name, we can see that if we use the role of "cell" we can check if the name and email are being rendered. but there are so many cells, but we can also optionally pass a 2nd param, a value, like this
const row = screen.getByRole("cell", { name: user.name });
so in the end we can iterate over the list of users and find out if this user is being rendered or not simply by using the .toBeInTheDocument() query
final test:
test("render the email and name of every user", () => {
const users = [
{ name: "Sam", email: "test1@test.com" },
{ name: "Jane 2", email: "test2@test.com" },
];
render(<UserList users={users} />);
for (const user of users) {
const name = screen.getByRole("cell", { name: user.name });
const email = screen.getByRole("cell", { name: user.email });
expect(name).toBeInTheDocument();
expect(email).toBeInTheDocument();
}
});
one issue i ran into was that, initially both the dummy users had the same email, so when that happened, the test failed
beforeEach()
so when we have multiple tests in a file, lets just say before a particular you want some setup done. thats where the beforeEach() fn comes into play
so whenever jest comes across a beforeEach block, its going to run it before EVERY TEST however it IS STRICTLY ADVISED THAT WE DO NOT render components here. its not an error, but a WARNING by rtl
beforeEach(() => {
// some setup
});
testing the App
import { render, screen } from "@testing-library/react";
import user from "@testing-library/user-event";
import App from "./App";
test("can receive a new user and show it on the screen", async () => {
render(<App />);
const nameInput = screen.getByRole("textbox", { name: /name/i });
const emailInput = screen.getByRole("textbox", { name: /email/i });
await user.click(nameInput);
await user.keyboard("brihadeesh");
await user.click(emailInput);
await user.keyboard("brihadeesh@test.com");
const button = screen.getByRole("button", { name: /add user/i });
await user.click(button);
const name = screen.getByRole("cell", { name: "brihadeesh" });
const email = screen.getByRole("cell", { name: "brihadeesh@test.com" });
expect(name).toBeInTheDocument();
expect(email).toBeInTheDocument();
});
to use the rtl cli book
to use the rtl cli book run npx rtl-book server filename.js now this filename can be any file name, basically all notes you type up will be saved here
and to re run this test book, you can just re run the same cmd again
when to use which query?
there are like 3 types of queries and they start with:
- getBy / getAllBy
- findBy / findAllBy
- queryBy / queryAllBy
- to find a single element we use getBy, findBy, queryBy
- to find multiple elements we use getAllBy, findAllBy, queryAllBy
when to use each?
- to prove an ele exists? getBy / getAllBy
- to prove an ele DOESNT exist? queryBy / queryAllBy
- to make sure an ele EVENTUALLY exists - findBy / findAllBy
if we use getBy and we find ele that is 0 or more than 1, we get an error and our test FAILS
now how do you write an expect block to test for failure?
expect(() => screen.getByRole("textbox")).toThrow()
findBy - operates async and it watches the op of our component over a span of 1s by default and every 50ms or so its going to try and find an ele, if it doesnt find it by the end of that 1s, its going to throw an error
here are some snippets of tests of how getBy queryBy and findBy react to finding
- 0 eles
- 1 ele
- more than 1 ele
0 eles
test("how getBy, findBy and queryBy react to finding 0 elements", async () => {
render(<ColorList />);
// getBy
expect(() => screen.getByRole("textbox")).toThrow();
// queryBy
expect(screen.queryByRole("textbox")).toEqual(null);
// findBy
let errorThrown = false;
try {
await screen.findByRole("textbox");
} catch (error) {
errorThrown = true;
}
expect(errorThrown).toEqual(true);
});
- now since the color list component doesnt have a textbox, we've to pass a callback fn to getByRole. otherwise itll throw an error
- and queryBy returns null if the ele doesnt exist, so thats why we have a toEqual(null) there
- and since findBy waits for a second to run, we put it in a catch block and catch the error to prevent the test from failing
1 ele
test("how getBy, findBy and queryBy react to finding 0 elements", async () => {
render(<ColorList />);
expect(screen.getByRole("list")).toBeInTheDocument();
expect(screen.queryByRole("list")).toBeInTheDocument();
expect(await screen.findByRole("list")).toBeInTheDocument();
});
- pretty straightforward test
more than 1 ele
test("how getBy, findBy and queryBy react to finding 0 elements", async () => {
render(<ColorList />);
// getBy
expect(() => screen.getByRole("listitem")).toThrow();
// queryBy
expect(() => screen.queryByRole("listitem")).toThrow();
// findBy
let errorThrown = false;
try {
await screen.findByRole("listitem");
} catch (error) {
errorThrown = true;
}
expect(errorThrown).toEqual(true);
});
looking for multiple eles
when looking for multiple eles, we use getAllBy, findAllBy and queryAllBy
test("how getAllBy, findAllBy and queryAllBy ", async () => {
render(<ColorList />);
// getBy
expect(screen.getAllByRole("listitem")).toHaveLength(3);
// queryBy
expect(screen.queryAllByRole("listitem")).toHaveLength(3);
// findBy
expect(await screen.findAllByRole("listitem")).toHaveLength(3);
});
query fn suffixes
always prefer to use fns that end with byRole. use others if byRole is not an option
- ByRole - finds eles based on their implicit or explicit ARIA role
- ByLabelText - find form eles based on the text their paired labels contain
- ByPlaceholderText - find form eles based upon their placeholder text
- ByText - find eles based on the text they contain
- ByDisplayValue - find eles based on their current value
- ByAltText - find eles based on their alt attr
- ByTitle - find eles based on their title attr
- ByTestId - find eles based on their data-testid attr (not really preferred, but if no option. go for it)
matchers in jest
note: you cant do a getByRole for a form component, if you want to get it, you must assign an ARIA label to it
matchers are what you chain to the expect block
in order to create a custom matcher, you need to first create a function with name of the matcher you wish to have
then this matcher by default gets 2 params, let me explain with an ex
expect(container).toHaveLength(3)
with the above example, if we decide to use a custom matcher, we'd get the container AND the value 3 as params
and how do we extend this and tell jest that we have such a matcher?
after we define the custom matcher, we simply do
expect.extend({name-of-matcher})
and then, we can use it!
but note that, this function must have a return block with the foll params
return {
pass: boolean,
message: () => string, //message to show the user in case the test fails
};
router context
when dealing with component tests in react porojs that use react router, it is essential that we wrap the component to be tested within a pair of router tags. otherwise youll get an err that says, an err occurred in the Link component/ something related to useHref being used in the context of a Router
render(
<MemoryRouter>
<RepositoriesListItem repository={repository} />
</MemoryRouter>
);
and there are 3 types of routers:
- BrowserRouter - stores the curr url in the address bar
- HashRouter - stores the url in the # part of the address bar
- MemoryRouter - stores the curr url in memory
many blogs/articles recc. memoryRouter for testing purposes. but eventually well replace it
when we wrap the component within a router component, the test passes but we still get an error, something regarding act(...)
if youre fetching data using useEffect and write tests for that component, you will eun into a lot of act warnings
important items to keep in mind
- unexpected state updates in a testing env are bad
- an example of this situation, a button click calls a fn that fetches a list of users
- this is how a normal flow would be, wrt testing, a testing flow of sorts
- find button -> simulate button click -> calls fn -> fake data fetch occurs. BUT AFTER THIS, the control immediately tries to find users on the screen and doesnt wait at all, even though this is supposed to be an ASYNC event
- so after the fetch occurs -> checks the screen for users -> NO USERS FOUND! TEST FAILS -> some time passes -> fake data req is completed -> users state is filled -> users are displayed
- so its like they're failing when they should be passing or they're passing when they should fail. either way its bad
- the act fn defines a window in time where state updates can and should occur
- so if any state changes happen within this act window, react is happy and this act fn runs and process all state updates + useEffects before exiting the act fn
- but if any change happens outside the act, we'll see the warnings
- RTL uses 'act' bts for us
- whenever we use findBy, findAllBy, waitFor, user.click, user.keyboard, since these are all async calls, RTL by default uses act BTS
- to solve act warnings, you should use findBy. usually you shouldnt follow the advice of a warning
- what this means is that, the warnings would say you need to use act() as a function, but dont.
- we dont use act along with RTL
options for solving act warnings
steps are from best to worst
- use a findBy or findAllBy to detect when the component has finished its data fetching
- now wtf and how tf will i know that i should use findBy and findAllBy?
- but one thing you should remember is, WHENEVER you have a component that is fetching and updating state in a useEffect. you should be prepared for ACT warnings
- so one thing that you could do is, write a pause fn that will purposely pause the code execution and then what you can do is have a screen.debug() before and after the pause
- that way you can inspect the html and see what has changed from before and after the pause fn
- once you know what has changed, you can write a find fn of that role and you go deeper in specificity by giving it a name
import { screen, render } from "@testing-library/react";
import RepositoriesListItem from "./RepositoriesListItem";
import { MemoryRouter } from "react-router-dom";
function renderComponent() {
const repository = {
full_name: "briha/project in py",
language: "js",
description: "this is a desc",
owner: "facebook",
name: "briha",
html_url: "https://github.com/facebook/react",
};
render(
<MemoryRouter>
<RepositoriesListItem repository={repository} />
</MemoryRouter>
);
}
test("to check if the link is being shown in the list item", async () => {
renderComponent();
await screen.findByRole("img", { name: /js/i });
});
const pause = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 100);
});
};
- use an act to control when the data fetching req gets resolved
- use a module mock to avoid rendering the troublesome component
- this method is to completely ignore the component that is giving us the act warning
- so one way to ditch is by using jest's mock functions, something we discussed earlier also
- so what we do is, for the jest function, we pass in the file to path that we want to ignore relative to the path where this test file exists, and then after that we have to tell what we want to render instead of this component
- for ex: if i want to ignore a component called A, i would create a mock fn, lets say called MA. now, this MA will have the path to A, and whichever component is calling this A component, will instead now call MA and will display whatever we put inside it
- so in a way, we're replacing the contents of that component with a component of our own
jest.mock("../tree/FileIcon", () => {
return () => {
return <div>FileIcon</div>;
};
});
- use act with a pause
- when you absolutely cannot get the act function to go away, you can use act with a pause
- and normally you wouldnt ever use this in a prod grade project, but literally worst case solution is here
test("to check if the link is being shown in the list item", async () => {
renderComponent();
await act(async () => {
await pause();
});
});
const pause = () => new Promise((res) => setTimeout(res, 100));
coming to the test itself
now time to test to check if the link exists or not for that, we can write
test("to check if the link is being shown in the list item", async () => {
const { repository } = renderComponent();
await screen.findByRole("img", { name: /js/i });
const link = screen.getByRole("link", { name: /github repository/i });
expect(link).toHaveAttribute("href", repository.html_url);
});
test("shows a fileicon with the appropriate icon", async () => {
const { repository } = renderComponent();
const icon = await screen.findByRole("img", { name: /js/i });
expect(icon).toHaveClass("js-icon");
});
test("shows a link to the code editor page", async () => {
const { repository } = renderComponent();
await screen.findByRole("img", { name: /js/i });
const link = await screen.findByRole("link", {
name: new RegExp(repository.owner.login),
});
expect(link).toHaveAttribute("href", `/repositories/${repository.full_name}`);
});
tests dealing with data fetching
we never want to actually make a network req when in a test env why?
- making reqs is usually slow, slow as in may take a few ms, but still in context of how fast testing is, its slow
- and if we make reqs, it might change the data and the test might fail
- we use fake or mock data fetching processes
options for data fetching
- mock the file that contains the data fetching code
- not really advised because, fine, we can mock it using jest. but whats to say we're calling this hook correctly? or we're getting the data from it correctl
- which is why we usually go for step 2 or step 3
- but that doesnt mean, we dont use step 1, we do but rarely
- use a lib to 'mock' axios and get axios to return fake data
- what this means is, we're still going to call the actual hook. but when its time to make the req, we intercept and request and we resolve it manually
- so to do this, we use a lib called MSW- mock service worker
- but how does this fit in? we're still going to make the api request and all. but msw is going to intercept this and grab the req and automatically respond to it
- msw setup:
- create a test file
- understand the exact url, method and return val of the req that our component makes
- create a msw handler to intercept that req and return some fake data for our component to use
- setup beforeAll, afterEach and afterAll hooks in the test file
- now these fns are there to make sure msw is actually being called
- in the test, render the component. wait for the ele to be visible
- create a manual mock of axios
what we've written so far
import { render, screen } from "@testing-library/react";
import { setupServer } from "msw/node";
import { rest } from "msw";
import { MemoryRouter } from "react-router-dom";
import HomeRoute from "./HomeRoute";
const handlers = [
rest.get("/api/repositories", (req, res, ctx) => {
const language = req.url.searchParams.get("q").split("language:")[1];
return res(
ctx.json({
items: [
{ id: 1, full_name: `${language}_one` },
{ id: 2, full_name: `${language}_two` },
],
})
);
}),
];
const server = setupServer(...handlers);
// going to be executed once before running all the tests in this file
beforeAll(() => {
server.listen();
});
// going to be run after every individual test in this file, regardless of it passing or failing
afterEach(() => {
// going to reset the handlers to their initial state
// note that we arent really modifying this or anything, but its just something that we have to give
server.restoreHandlers();
});
// going to be executed once after running all the tests in this file
afterAll(() => {
server.close();
});
// jest.mock("", () => {
// return () => {
// return <div>hi</div>
// }
// })
function renderFunction() {
render(
<MemoryRouter>
<HomeRoute />
</MemoryRouter>
);
}
test("renders 2 links for each language", async () => {
renderFunction();
const languages = [
"javascript",
"typescript",
"rust",
"go",
"python",
"java",
];
// loop over every lang
for (let language of languages) {
// for every lang, make sure we see 2 links
const links = await screen.findAllByRole("link", {
name: new RegExp(`${language}_`),
});
expect(links).toHaveLength(2);
// assert that the links have the appropriate full_name
expect(links[0]).toHaveTextContent(`${language}_one`);
expect(links[1]).toHaveTextContent(`${language}_two`);
expect(links[0]).toHaveAttribute("href", `/repositories/${language}_one`);
expect(links[1]).toHaveAttribute("href", `/repositories/${language}_two`);
}
});
const pause = () => new Promise((res) => setTimeout(res, 100));
now as demo'd above, creating a server, handlers and everything else is a cumbersome ngl we've to call beforeAll, afterEach and afterAll and this gets annoying when we have multiple test files that make fake api requests
so the goal here is to refactor all of this logic into a function called createServer() that takes in a config and in that config you could tell it the path, the method, and what kind of response youd like back so this way you dont have to rewrite a lot of code and its just easier
this is the fn
import { setupServer } from "msw/node";
import { rest } from "msw";
/**
TO
createServer([
{
path: '',
method: '',
res: (req, res, ctx) => {
return {
items: [{}, {}]
}
}
}
])
FROM
const handlers = [
rest.get("/api/repositories", (req, res, ctx) => {
const language = req.url.searchParams.get("q").split("language:")[1];
return res(
ctx.json({
items: [
{ id: 1, full_name: `${language}_one` },
{ id: 2, full_name: `${language}_two` },
],
})
);
}),
];
*/
export function createServer(handlerConfig) {
// map through every ele in this config and create a handler for it
const handlers = handlerConfig.map((config) => {
return rest[config.method || "get"](config.path, (req, res, ctx) => {
return res(ctx.json(config.res(req, res, ctx)));
});
});
const server = setupServer(...handlers);
beforeAll(() => {
server.listen();
});
afterEach(() => {
server.restoreHandlers();
});
afterAll(() => {
server.close();
});
}
describe
a describe fn lets us nest tests and it has a few imp results
- code organisation
- it scopes the beforeAll, afterEach and afterAll and are going to be scoped only to the tests inside the describe block
fixing and figuring out bugs from libs that you just dont know
options for debugging tests
- use
test.onlyordescribe.onlyto limit the number of tests executed- running
test.onlywill only run that test in a file with multiple tests - running
describe.onlywill only run that nest of tests inside the desc block
- running
- setup a debugger
- by simply placing
debugger;anywhere in the code and running the debug script"test:debug": "react-scripts --inspect-brk test --runInBand --no-cache"and then visitingabout:inspectin the browser will open the debugger tool and pressing the playbutton will help us inspect the values stored in the elements and help us debug the issue
- by simply placing
- console logs
and ive checked another thing i wished to learn, im going to be honest, im still not very confident in my testing skills but onwards and upwards, ill start practising testing with projects i build from here on. link to my certificate: here
and i'm also interested in checking out this React Advanced course by the Meta Team on coursera, it has some stuff on HOC's which i have no idea on and id also like to get more familiar with some of the other hooks offered by React. useMemo, useReducer and such, ones that i dont use as frequently
i'll update this article if i end up taking up that react course this month!
and ofcourse, you can check out the code in the github repository, i believe ive referenced it somewhere on the homepage. if not, on my github profile, search for a repo called "the notebook"
tiny notes
- instead of test() you can also use
it() - and, instead of using
userfrom@testing-library/user-events, you can usefireEventfrom@testing-library/reactand do pretty much the same things.- but, one change from what i know so far, there's no fireEvent.keyboard()
- you're supposed to do this:
fireEvent.change(element, {target: { value: "Enter the string to be typed inside the input"}}) - youre basically changing the target.value property of the input, which is what is actually happening.
- and this doesnt have to be async
tbh, that coursera advanced react thing wasn't as helpful as i thought it'd be (for me), so here's a list of concepts that i want to learn, and i'll either write articles/ upload videos on yt about the same
- hooks
- useState
- useEffect - useLayoutEffect
- useReducer
- useMemo
- useCallback
- useRef
- useContext
- react HOC
- SWR
- useDebounce
- react query - as much as i can!
NOTE: you can find articles on hooks ive mentioned above in my blog here