Skip to content

evanmarshall-dev/udemy_vitest

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Udemy: React Testing Library - Vitest

Section: Introduction

Module: First Test 🥇

  1. complete environment setup found in the README.md in vite-starter in the course repo.

App.test.jsx

This file contains a test for the App component to be run in the CLI using: pnpm test.

Breaking down the syntax:

// file: ./src/App.test.jsx

import { render, screen } from "@testing-library/react";
import App from "./App";

test("App contains correct heading", () => {
  render(<App />);
  const headingElement = screen.getByText(/learn react/i);
  expect(headingElement).toBeInTheDocument();
});
  • render: The render method comes from @testing-library/react and creates the simulated DOM. It allows the test to understand what we are testing against. It creates the simulated DOM for whatever JSX you give it as an argument (i.e. <App /> component).
  • screen: Once it has been rendered the simulated DOM can be accessed via the global object called screen (Also imported from @testing-library/react).
  • screen.getByText: Is a method of screen. It is looking at the simulated DOM and it is trying to find an element that matches the text we give it as an argument (i.e. /learn react/i). In this example we passed in a regular expression (regex) denoted by the forward slashes and there is an i flag to say it is case insensitive (The argument can also be an exact string, but regex are more flexible).
  • const headingElement: If there is any element within the rendered simulated DOM whose display text matches the regex it will be stored in the headingElement variable.
  • expect(headingElement).toBeInTheDocument(): Is not part of the testing library. It is part of vitest (Syntax is the same in Jest). This is asserting and can cause the test to succeed or fail. We are asserting that the headingElement returned by the getByText method is in the document. If it is, the statement will be true and the test will succeed otherwise the test will fail.

Note

Running Vitest in --watch mode will auto re-run test when a change has been made.

Module: Assertions ✊

Assertions determine whether a test passes or fails.

Syntax of an Assertion

  • expect: All assertions start with an expect method which is a global in Jest or Vitest.
  • expect(argument): The argument is what you are asserting against. It is what Vitest will examine to see if it meets our expectation.
  • toBeInTheDocument: Is a matcher and is what the assertion type is.
  • toBeInTheDocument(argument): Sometimes there is an argument passed into the matcher. To be in the document does not have an argument (It is either in the document or not). Sometimes you are comparing the element to some sort of known quantity which would need an argument.

More Examples of Assertion

  • expect(element.textContent).toBe('hello');: Most likely using a screen method to find the element on the page. Text content is self explanatory and the matcher (toBe) takes a argument for an exact string (i.e. hello).
  • expect(elementsArray).toHaveLength(7);: The elementsArray would have been defined in the previous line. It has a matcher of toHaveLength with an argument of 7.

jest-DOM

Can also be used with Vitest as well as Jest. If it is used it needs to be imported prior to each test in order to be able to use the matchers with jest-DOM.

Both Jest and/or Vitest use a setup file to import the jest-DOM for each test (/src/setupTests.js).

Once it is imported it allows you to use matchers specific to the DOM. The above examples show more general matchers. Below are DOM-specific matchers:

  • toBeVisible()
  • toBeChecked()
  • toBeInTheDocument()

Module: How Tests Work 👷‍♂️

There is a division of responsibilities between React Testing Library (RTL) and Jest/Vitest.

RTLs job is to create a simulated DOM for your components which you can then use for interactions and assertions.

You cannot use RTL without a test runner. A test runner will find the tests, run the test, and make assertions. This is where Jest/Vitest comes in.

Note

  • Vitest is 3x - 5x faster than Jest.
  • Jest is harder to configure for Vite.
  • However Jest tends to work better with Next.js.
  • Less advanced syntax is virtually identical between the two test runners, only the setup differs.

Both Jest/Vitest have a global test method that takes two arguments:

  1. A string description of the test.
  2. A test function to run for test pass/fail.

The test fails if there is an error when the second argument function runs.

Assertions throw errors when the expectation fails. If no error thrown then the test passes (Meaning an empty test passes or a test where everything inside the global test method is empty).

// file: ./src/App.test.jsx

// OTHER CODE...

// Empty Test.
test("Empty Test", () => {});

// Throw Error Explicitly.
test("Test throws error explicitly", () => {
  throw new Error("Fail this test!");
});

With RTL the part of the code that will throw an error is the assertion.

// file: ./src/App.test.jsx

// OTHER CODE...

// RTL Assertion Error.
test("App contains correct heading", () => {
  render(<App />);
  const headingElement = screen.getByText(/learn react/i);
  // Error thrown with .not.
  expect(headingElement).not.toBeInTheDocument();
});

Module: Test Driven Development (TDD) đź§Ş

Writing tests before writing the code then write the code according to specs set by the tests to make the tests pass.

Red-Green Testing

You want the tests to fail (red tests) before the code is written the after the code is written you see passing tests (green tests).

  1. You write an empty function or empty functional React component.
  2. You write your tests and expect them to fail because empty functions.
  3. Write your code and expect the tests to pass.

Module: React Testing Library (RTL) Philosophy 👨‍🎓

It is opinionated or drives you towards best practices when React testing.

What Does RTL Do?

  • Creates a virtual DOM for testing.
    • Provides utilities for interacting with said DOM (i.e. Find elements in the DOM or interact with elements).
  • Allows for testing without a browser.

Types of Tests

  • Unit Tests.
    • Test one unit of code in isolation.
  • Integration Tests.
    • Tests how multiple units work together (i.e. Interaction between components).
  • Functional Tests.
    • Tests a particular function of the software (You are not testing your code but rather its behaviour). RTL encourages this type of test.
  • Acceptance/End-to-End Tests (E2E).
    • These tests require a browser and server that your app is connected to (Require a special tool such as Cypress or Selenium). RTL is not built for these types of tests.

Module: Functional Testing vs Unit Testing 📊

Unit Testing

With unit testing you want your tests to be as isolated as possible. You do this by mocking your components dependencies (Use test version of a function for example that the component relies on). Sometimes you also test internals (i.e. Testing differences it made to the state because you do not have other components to see what it did to the app).

Pro: Since unit testing is isolated it is easier to pinpoint failures.

Con: It is further from how a user would interact with your software. It is also more likely to break with refactoring (Refactoring: Change how code is written, but not its functionality and with unit testing you are testing how the code is written).

Functional Testing (RTL Preferred)

With functional testing you include all relevant units for a particular behaviour or user flow.

Pro: Close to how a user would interact with your software. They are also more robust tests so if you refactor it should still pass.

Con: More difficult to debug failing tests because they aren't as tightly coupled to the code.

Note

There is also such thing as Behaviour Driven Development (BDD), but it requires multiple teams and collaboration.

Module: How Testing Library Finds Elements on the Page 🔥

Testing library suggests to find elements using accessibility handles. This is a better way to test as well as ensuring that your app is accessible to users.

getByRole is the top priority recommended. Some roles are: MDN ARIA Roles.

Other accessibility options if getByRole is not available (i.e. for form inputs) you can use getByLabelText or getByPlaceholderText.

Semantic queries such as getByAltText and getByTitle are also good.

Test IDs are a last resort (i.e. getByTestID).

Back in our code for App.test.jsx we are using the frowned upon getByText method. We can switch it to an ARIA: heading role to utilize accessibility handles. When you use getByRole there are several options, but the most common one to use is name which can also take either a regex or string.

For example:

// file: ./src/App.test.jsx

// OTHER CODE...

test("App contains correct heading", () => {
  render(<App />);
  // const headingElement = screen.getByText(/learn react/i);
  // SWITCH TO ROLE
  const headingElement = screen.getByRole("heading", { name: /learn react/i });
  expect(headingElement).toBeInTheDocument();
});

Section: App - Color Button

Module: Getting Started with App

  1. Remove the h1 element in App.jsx (<h1>I'm gonna learn React Testing Library</h1>) and the sample test in App.test.jsx.
  2. In App.test.jsx we will now create three tests that all start with the keyword test.
  3. Test #1 will check to see if the button starts with the correct color. First argument is the test description and the second argument is the function that determines pass/fail.
  4. Test #2 will check if the button starts with the correct text.
  5. Test #3 and #4 will duplicate the first two tests but for after the button click.
  6. Next thing we do is render and we will render the app component.
  7. Then we will use screen to find the element we are looking for (i.e. button). We will append .getByRole to screen and pass in the role of button to getByRole. Save the screen into a variable (i.e. buttonElement).
  8. To determine color we will use the jest-DOM matcher of toHaveClass(). First we pass in the buttonElement constant into expect and append .toHaveClass() to expect. Pass in a string of "red" to toHaveClass to represent a class of red (Anticipating making a class with the name of red and assigning the button that class initially).
  9. The arguments for getByRole are creating assertions. We can have more than one assertion, but it is not necessary in a front end test so we can just add it as a second argument to getByRole in the first assertion. The second assertion will be as follows: {name: /blue/i} since the button will start out with a text change to blue.

Currently we will have the first test fail because we have no buttons with the text of blue and two tests will pass which are the last two empty tests (The red part of red-green testing).

For example:

// file: ./src/App.test.jsx

// OTHER CODE...

// BEFORE CLICKING BUTTON.
test("button starts with correct color", () => {
  render(<App />);
  const buttonElement = screen.getByRole("button", {
    name: /blue/i,
  });
  expect(buttonElement).toHaveClass("red");
});

// ? test("button starts with correct text", () => {});

// AFTER CLICKING BUTTON.
test("button has correct color after click", () => {});

test("button has correct text after click", () => {});

Module: Method for Debugging Roles logRoles

Let's say you did not know the role was button we can use logRoles from testing library DOM.

  1. If we want to see what the roles are in the app we can destructure container from the output of the render: const { container } = render(<App />);.
  2. Run logRoles on the above container: logRoles(container);.

For example:

// file: ./src/App.test.jsx

import { render, screen } from "@testing-library/react";
import { logRoles } from "@testing-library/dom";
import App from "./App";

test("button starts with correct color", () => {
  const { container } = render(<App />);
  logRoles(container);
  const buttonElement = screen.getByRole("button", {
    name: /blue/i,
  });
  expect(buttonElement).toHaveClass("red");
});

test("button has correct color after click", () => {});

test("button has correct text after click", () => {});

The logRoles now provides some output. A button and what its name is:

# CLI Output

button:

Name "Change to blue":
<button
  class="red"
/>

Note

It is not much because of only one component, but useful in large apps when you are not sure of all the roles.

Module: Test Behaviour with Button Click

It is common to have longer tests with multiple assertions that test a particular flow.

To click the button we can use fireEvent from testing library react (There is a more involved userEvent which we will look at later).

  1. Add fireEvent and append .click() method.
  2. Pass in what to click, which is the buttonElement.
  3. Skip down a step and add the assertion to check button color after click. We will use the same as before click, but toHaveClass of blue instead of red: expect(buttonElement).toHaveClass("blue");.
  4. For checking the button text we will be using a new matcher from jest-DOM (toHaveTextContent). We always start an assertion with expect, then we will pass in the buttonElement, append .toHaveTextContent(), and pass a regex for text of red into the new matcher: expect(buttonElement).toHaveTextContent(/red/i);.

Since we are currently in the red portion of red-green testing the test will fail. When the button is clicked the text content does not include regex of red.

For example:

// file: ./src/App.test.jsx

// OTHER CODE...

test("Button click flow", () => {
  // Render the app.
  render(<App />);
  // Find the button.
  const buttonElement = screen.getByRole("button", {
    name: /blue/i,
  });
  // Check initial color.
  expect(buttonElement).toHaveClass("red");

  // Click the button.
  fireEvent.click(buttonElement);

  // Check button text.
  expect(buttonElement).toHaveTextContent(/red/i);

  // Check button color.
  expect(buttonElement).toHaveClass("blue");
});

Module: Change Button Color on Click with React

We just finished the failing/red code for testing.

  1. Go to App.jsx and within the App component we will use state to track what the button color is.
  2. Assign useState to destructured buttonColor state variable and setButtonColor setter function. The initial state for the button will be "red": const [buttonColor, setButtonColor] = useState("red");.
  3. Instead of the button className being "red" we will assign it to whatever the state variable buttonColor is: <button className={buttonColor}>Change to blue</button>.
  4. The button text content will display whatever the opposite of the button color is. We can derive that from the current state and call it nextColor. If the button color is red (buttonColor === "red") then next color is blue (? "blue"), else the next color is red (: "red") using ternary operators: const nextColor = buttonColor === "red" ? "blue" : "red";.
  5. We now have to give the button an on click event. The onClick takes a return function that returns the setter function (setButtonColor) with a value passed in of nextColor: <button className={buttonColor} onClick={() => setButtonColor(nextColor)}>. We change the color button state to whatever the nextColor is on click.
  6. Lastly, we need to make the blue class and set styles in App.css.

For example:

// file: ./src/App.jsx

// OTHER CODE...

function App() {
  // State variables.
  const [buttonColor, setButtonColor] = useState("red");

  // Constants.
  const nextColor = buttonColor === "red" ? "blue" : "red";

  return (
    <div>
      {/* <button className="red">Change to blue</button> */}
      {/* <button className={buttonColor}>Change to blue</button> */}
      <button className={buttonColor} onClick={() => setButtonColor(nextColor)}>
        Change to {nextColor}
      </button>
    </div>
  );
}

// OTHER CODE...

Note

You will notice that the tests pass even before we add the blue button color styles. It is quite complicated to test for actual styles versus testing for classes.

Testing for Styles

To test for styles you need to make sure the css is being interpreted as part of your tests.

  • In the vite.config.js it is one line that says: css: true,.
  • With Jest it is quite slow and requires some extra plugins.
  • It is not always obvious what the styles come out as. For example if we switch the last assertion in our test to: expect(buttonElement).toHaveStyle({"background-color": "blue"}); then it will fail because the blue style will render as an RGB value (rgb(0, 0, 255)) instead of "blue".
  • It is more straightforward to just test for classes and use visual regression testing to catch any visual style (More advanced).

Module: Button and Checkbox Test

Now we will add a checkbox that when checked the button is disabled and enabled when unchecked.

We will make a new test to separate the button click flow from the checkbox flow.

  1. Back in App.test.jsx we will add a new test. Start with using test global from Vitest which takes two arguments (1. The name of the test, 2. function for pass/fail): test("Checkbox flow", () => {});.
  2. Start by rendering something, usually a component: render(<App />);.
  3. We need to find the button and the checkbox because we want to check initially that the checkbox is checked and the button is enabled.
  4. Find button by using the screen object (That has access to the simulated DOM that the render created). We then append .getByRole() and pass in "button", and second argument is the expectant name as regex blue (Because the button starts out red and changes to blue): const buttonElement = screen.getByRole("button", { name: /blue/i });.
  5. Find the checkbox. Follow the same steps as finding the button, use checkbox role passed into getByRole(), second argument being name (Even though name is not necessary because there is only one checkbox on the page), and assign to variable checkboxElement. The name will be the accessible name for the checkbox which will be the label for the input: const checkboxElement = screen.getByRole("checkbox", {name: /disable button/i,});.
  6. Initial conditions are the button enabled and checkbox unchecked. Start by creating an assertion which starts with an expect. Pass in buttonElement to expect and append a matcher to this (i.e. toBeEnabled for button and toBeChecked for checkbox). The toBeEnabled() has no argument because it is either enabled or disabled: expect(buttonElement).toBeEnabled();.
  7. Another assertion that checks NOT condition for toBeChecked since there is no built in toNotBeChecked: expect(checkboxElement).not.toBeChecked();.

For example:

// file: ./src/App.test.jsx

// OTHER CODE...

test("Checkbox flow", () => {
  render(<App />);

  // Find elements.
  const buttonElement = screen.getByRole("button", { name: /blue/i });
  const checkboxElement = screen.getByRole("checkbox", {
    name: /disable button/i,
  });

  // Check initial conditions.
  expect(buttonElement).toBeEnabled();
  expect(checkboxElement).not.toBeChecked();
});

Check that the tests are currently red (It cannot find the checkbox because it has not be created yet).

Add the React code

  1. Go to App.jsx and add a checkbox input. Make sure to add id of disabled-button-checkbox (This will make it accessible to screen readers and our tests).
  2. Set the checkbox to not be checked by default: <input type="checkbox" id="disabled-button-checkbox" defaultChecked={false} />.
  3. Add label and associate it to the checkbox input id using htmlFor: <label htmlFor="disable-button-checkbox">Disable Button</label>.

For example:

// file: ./src/App.jsx

// OTHER CODE...

<input
  type="checkbox"
  id="disable-button-checkbox"
  defaultChecked={false}
/>
<label htmlFor="disable-button-checkbox">Disable Button</label>

// OTHER CODE...

Now the tests are passing or green.

Module: Code Quiz Button Checkbox Logic and Test

Tests

  • Add to the current test.
  • We want to use fireEvent twice. When the checkbox is checked and when the checkbox is unchecked.
  • Check that the button is enabled when checkbox is unchecked (Done) and that the button is disabled when the checkbox is checked.
  • For assertion matchers on the button we will again use toBeEnabled() and toBeDisabled().

React Logic

  • Checkbox controls the button via a boolean state.
  • State will determine the value of disabled attribute on the button.
  • Calling the state variable disabled and initial value of state to be set to false.
  • The onChange for the checkbox will set that state to whether the target (checkbox) of the event is checked or not: {(e) => setDisabled(e.target.checked)}.

Code Quiz Solution

  1. Add to the checkbox flow test.
  2. Use fireEvent.click on the checkboxElement followed by expectation that the buttonElement will be disabled.
  3. Use fireEvent.click on the checkboxElement again followed by an expectation that the buttonElement will be re-enabled.

For example:

// file: ./src/App.test.jsx

// OTHER CODE...

test("Checkbox flow", () => {
  render(<App />);

  // Find elements.
  const buttonElement = screen.getByRole("button", { name: /blue/i });
  const checkboxElement = screen.getByRole("checkbox", {
    name: /disable button/i,
  });

  // Check initial conditions.
  expect(buttonElement).toBeEnabled();
  expect(checkboxElement).not.toBeChecked();

  // Check the checkbox to disable button.
  fireEvent.click(checkboxElement);
  expect(buttonElement).toBeDisabled();

  // Click checkbox again to re-enable button.
  fireEvent.click(checkboxElement);
  expect(buttonElement).toBeEnabled();
});
  1. Create new state called isDisabled: const [isDisabled, setIsDisabled] = useState("false");.
  2. The defaultChecked prop (Checked or unchecked) on the checkbox will rely on the state isDisabled: <input defaultChecked={isDisabled} />.
  3. When the checkbox changes (onChange) we update the state: <input onChange={(e) => setIsDisabled(e.target.checked)} />.
  4. Also use the isDisabled state to determine whether the button is disabled or not: <button disabled={isDisabled}>...</button>.

For example:

// file: ./src/App.jsx

// OTHER CODE...

const [buttonDisabled, setButtonDisabled] = useState(false);

// OTHER CODE...

<button
  className={buttonColor}
  onClick={() => setButtonColor(nextColor)}
  disabled={buttonDisabled}
>
  Change to {nextColor}
</button>
<br />
<input
  type="checkbox"
  id="disable-button-checkbox"
  // defaultChecked={disabled}
  checked={buttonDisabled}
  onChange={(e) => setButtonDisabled(e.target.checked)}
/>

// OTHER CODE...

Code Quiz 2 Solution (Button Grey when Disabled)

  1. Add assertion when checkbox is checked to check if button has class of "gray": expect(buttonElement).toHaveClass("gray");. Add to original checkbox flow.
  2. Add assertion when checkbox is unchecked to check if button has class of "red": expect(buttonElement).toHaveClass("red");. Add to original checkbox flow.
  3. Create a new checkbox flow for what happens after the button has been clicked (i.e. turns blue). The new checkbox flow will be the same as original except that we add a fireEvent.click(buttonElement) after we find the elements and remove the checking of initial conditions for checkbox and button.

For example:

// file: ./src/App.test.jsx

// OTHER CODE...

test("Checkbox flow after button clicked", () => {
  render(<App />);

  // Find elements.
  const buttonElement = screen.getByRole("button", { name: /blue/i });
  const checkboxElement = screen.getByRole("checkbox", {
    name: /disable button/i,
  });

  // Click the button.
  fireEvent.click(buttonElement);

  // Check the checkbox to disable button.
  fireEvent.click(checkboxElement);
  expect(buttonElement).toBeDisabled();
  expect(buttonElement).toHaveClass("gray");

  // Click checkbox again to re-enable button.
  fireEvent.click(checkboxElement);
  expect(buttonElement).toBeEnabled();
  expect(buttonElement).toHaveClass("red");
});

Tip

For test code it is less important for the code to be DRY than being able to debug failing tests quickly (Readable).

  1. Create className constant equal to condition of buttonDisabled would be "gray" and if not it will be set to whatever the buttonColor state is: const className = buttonDisabled ? "gray" : buttonColor;.
  2. Assign the constant className to the className of the button: <button className={className}>...</button>.
  3. Add styles to App.css for the background color of gray.

Module: Unit Testing Functions

Sometimes you will have functions separate from components due to being used by several other components or because it is complex and needs modularity.

If we were to use more complicated colors classes (i.e. .midnight-blue) it will work when we have to set the button color (useState=("midnight-blue")), but it will not look great when we are looking at the button text.

  1. Create a file called helpers.js.
  2. Within helpers.js add an export for a function that converts kebab case to title case. Leave the function empty: export function kebabCaseToTitleCase() {}.
  3. In the App.test.jsx we are going to add a new global called describe() (A way to group tests). It takes the same two arguments as test() and it allows us to route multiple tests within it's second argument function.
  4. Within describes second argument we will add a test for if it works with no hyphens, one hyphen, and multiple hyphens.
  5. In the no hyphen test we write an assertion to expect kebab case helper function lowercase "red" to be uppercase "Red": expect(kebabCaseToTitleCase("red")).toBe("Red");.
  6. For one hyphen we will look at midnight-blue class name, which is what we will be working with in the state. We will use the helper function to translate that to "Midnight Blue".
  7. Lastly, for multiple hyphens we will look at medium-violet-red class name.

We will have 6 failing tests at this point due to the buttonColor name changes and the three grouped tests in the describe global.

Write the Helper Function Code

  1. The kebabCaseToTitleCase() function needs to take an argument (i.e. colorName).
  2. We will replace the hyphens with spaces: const colorWithSpaces = colorName.replaceAll("-", " ");.
  3. Change the beginning letter of each word to a capitalized letter. We will pass in a regex to this with out of word boundary (/\b), a lowercase letter (([a-z])) in parenthesis so we can catch the lowercase letter, find them all (/g), and replace with whatever the match was to uppercase (match.toUpperCase()).
  4. Then we will return the result (colorWithCaps).

For example:

// file: ./src/helpers.js

export function kebabCaseToTitleCase(colorName) {
  const colorWithSpaces = colorName.replaceAll("-", " ");
  const colorWithCaps = colorWithSpaces.replace(/\b([a-z])/g, (match) =>
    match.toUpperCase()
  );

  return colorWithCaps;
}

Now all of the unit tests will be passing.

Module: Code Quiz Colors with Spaces

Tests 2

  • Check that color starts with mediumvioletred and changes to midnightblue.
  • Update existing tests.
  • After updating the class names in the toHaveClass assertions the checkbox disabling tests should still pass (free regression testing / free testing when code changes).

Code Quiz Solution 2

Note

We do not have to change any of the name options because they are not very specific (Had keyword and case insensitive).

  1. Button Click Flow: Update the toHaveClass to the new color class names for before and after click (i.e. expect(buttonElement).toHaveClass("medium-violet-red");).
  2. Checkbox Flow: Update the toHaveClass to the new color class names for checkbox click to re-enable button.
  3. Checkbox Flow After Button Clicked: Update the toHaveClass to the new color class names for checkbox click to re-enable button.

App.jsx

  1. Create new constant for next color and title case and assign it to kebab case helper function with nextColor passed in as an argument. This applies the helper function to whatever the next color is.
  2. Update the nextColor constant name to nextColorClass.
  3. For the button text content change {nextColor} to {nextColorTitleCase}.

Module: When to Unit Test

  • If the logic is complex and difficult to test via functional tests.
  • If there are too many edge cases.
  • When determining what caused a functional test to fail.
  • Functional tests are high level which makes them resistant to refactored code, which is good, except when you are trying to diagnose when a test fails.

Module: Recap Color Button Checkbox App

  • Test interactivity from fireEvent, an object imported from RTL that has methods on it like click.
  • Used several new jest-DOM assertions:
    • toBeEnabled()
    • toBeDisabled()
    • toBeChecked() (Note: We used .not.toBeChecked() for the opposite)
  • We used the name option for getByRole to identify which checkbox and which button we were referring to.
  • We used the describe global from Vitest to group tests into logical groups.
  • Discussed unit testing functions, demoed, and talked about when to use them.

About

Track Udemy React Testing Library with Vitest course progress and document key concepts.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published