generated from btholt/next-course-starter
-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
231 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,28 +14,13 @@ They designed it to be a drop-in replacement for [Jest][jest] which is what I ha | |
While Vitest is not using Jasmine directly, its APIs mimic Jasmine APIs (just like Jest.) | ||
|
||
Let's get going Run `npm install -D [email protected] @vitest/[email protected] [email protected] [email protected]`. | ||
Let's get going. Run `npm install -D [email protected] @testing-library/[email protected] [email protected]`. | ||
|
||
> This course previously taught [@testing-library/react][tlr] and used a synthetic DOM tool called [happy-dom][hd]. Vitest now has a first class integration with [Playwright][playwright] and their own version @testing-library/react, vitest-browser-react. Hence this version will use those tools. If you want to learn those other tools, the [v8][v8] version of this course all still works. | ||
`@testing-library/react`, formerly called `react-testing-library`, is a tool that has a bunch of convenience features that make testing React significantly easier and is now the recommended way of testing React, supplanting [Enzyme][enzyme]. Previous versions of this course teach Enzyme if you'd like to see that (though I wouldn't recommend it unless you have to.) | ||
|
||
We are going to be using the latest-and-greatest testing tool from Microsoft called Playwright, the spirtual successor to Google's Puppeteer tool. Using this tool, your command line client will spin up a _real_ copy of Webkit, Firefox, or Chromium and run your tests inside of them. Can't beat the real thing, and it's finally fast and not-flaky enough for it to be worth it! | ||
We need to tell Vitest that we need a browser-like environment which it will fulfill via the [happy-dom][hd] package. happy-dom is a lot like jsdom but smaller, doesn't do 100% of what the browser does, and is much, much faster. | ||
|
||
Okay, let's go set up Vitest to work with our Vite project. In your `vite.config.js` | ||
|
||
```javascript | ||
|
||
// under server | ||
test: { | ||
setupFiles: ["vitest-browser-react"], | ||
browser: { | ||
enabled: true, | ||
name: "firefox", // or chromium or webkit | ||
provider: "playwright", | ||
}, | ||
}, | ||
``` | ||
|
||
If you've never run Playwright before, you should run `npx playwright install` to install all the browser stuff necessary. | ||
Next go into your src directory and create a folder called `__tests__`. Notice that's double underscores on both sides. Why double? They borrowed it from Python where double underscores ("dunders" as I've heard them called) mean something magic happens (in essence it means the name itself has significance and something is looking for that path name exactly.) In this case, Vitest assumes all JS files in here are tests. | ||
|
||
Let's go add an npm script. In your package.json. | ||
|
||
|
@@ -45,28 +30,26 @@ Let's go add an npm script. In your package.json. | |
|
||
> Fun trick: if you call it test, npm lets you run that command as just `npm t`. | ||
This command let's you run Vitest in an interactive mode where it will re-run tests selectively as you save them. This lets you get instant feedback if your test is working or not. This is probably my favorite feature of Vitest. So try it now. It should error because we have no tests, but it should open a browser with the testing harness loaded. | ||
This command let's you run Jest in an interactive mode where it will re-run tests selectively as you save them. This lets you get instant feedback if your test is working or not. This is probably my favorite feature of Vitest. | ||
|
||
```bash | ||
# these all do the same thing | ||
npm run test | ||
npm test | ||
npm t | ||
``` | ||
Okay, one little configuration to add to your vite.config.js | ||
|
||
Also good to go install the [Vitest VS Code extension][vitest-vsc] if you're using VS Code. | ||
```javascript | ||
// add this to the config object | ||
test: { | ||
environment: "happy-dom", | ||
}, | ||
``` | ||
|
||
Now that we've got that going, let's go write a test. | ||
|
||
[vitest-vsc]: https://marketplace.visualstudio.com/items?itemName=vitest.explorer | ||
[jest]: https://jestjs.io | ||
[jasmine]: https://jasmine.github.io/ | ||
[enzyme]: http://airbnb.io/enzyme/ | ||
[istanbul]: https://istanbul.js.org | ||
[res]: https://raw.githubusercontent.com/btholt/complete-intro-to-react-v5/testing/__mocks__/@frontendmasters/res.json | ||
[app]: https://github.com/btholt/citr-v8-project/tree/master/14-context | ||
[fb]: https://twitter.com/cpojer/status/1524419433938046977 | ||
[hd]: https://github.com/capricorn86/happy-dom | ||
[vitest]: https://vitest.dev/ | ||
[v4]: https://frontendmasters.com/courses/intermediate-react-v4/setup-jest-testing-library/ | ||
[tlr]: https://github.com/testing-library/react-testing-library | ||
[v8]: https://frontendmasters.com/courses/intermediate-react-v5/setup-react-testing-library-vitest/ | ||
[playwright]: https://playwright.dev/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
--- | ||
description: "" | ||
--- | ||
|
||
Let's write our first test for Pizza.jsx. In general, here's my methodology for testing React: | ||
|
||
- Try to test functionality, not implementation. Make your tests interact with components as a user would, not as a developer would. This means you're trying to do more to think of things like "what would a user see" or "if a user clicks a button a modal comes up" rather than "make sure this state is correct" or "ensure this library is called". This isn't a rule; sometimes you need to test those things too for assurance the app is working correctly. Use your best judgment. | ||
- Every UI I've ever worked on changes a lot. Try to not unnecessarily spin your wheels on things that aren't important and are likely to change. | ||
- In general when I encounter a bug that is important for me to go back and fix, I'll write a test that would have caught that bug. Actually what I'll do is _before_ I fix it, I'll write the test that fails. That way I fix it I'll know I won't regress back there. | ||
- Ask yourself what's important about your app and spend your time testing that. Ask yourself "if a user couldn't do X then the app is worthless" sort of questions and test those more thoroughly. If a user can't change themes then it's probably not the end of the world (a11y is important) so you can spend less time testing that but if a user can't log in then the app is worthless. Test that. | ||
- Delete tests on a regular basis. Tests have a shelf life. | ||
- Fix or delete flaky tests. Bad tests are worse than no tests | ||
|
||
Okay, create a new file called `Pizza.test.jsx`. This naming convention is just habit. `Pizza.spec.jsx` is common too. But as long as it's in the `__tests__` directory it doesn't much matter what you call it. | ||
|
||
```javascript | ||
import { render } from "@testing-library/react"; | ||
import { expect, test } from "vitest"; | ||
import Pizza from "../Pizza"; | ||
|
||
test("alt text renders on image", async () => { | ||
const name = "My Favorite Pizza"; | ||
const src = "https://picsum.photos/200"; | ||
const screen = render( | ||
<Pizza name={name} description="super cool pizza" image={src} /> | ||
); | ||
|
||
const img = screen.getByRole("img"); | ||
expect(img.src).toBe(src); | ||
expect(img.alt).toBe(name); | ||
}); | ||
``` | ||
|
||
This is your most basic test. It renders a React component, and then starts running assertions on it. Here we're asserting that our pizza image ends up with the correct alt text on it. Not the most useful test but a good start for us seeing how to assert something. I could see this being useful if we had bugs that occasionally alt text wasn't showing up. | ||
|
||
We're using getByRole here to grab the image on the page. In general React Testing Library wants you to adopt a user-centric mindset of how you're asserting things on the page. In thise case we're trying to find an image which is something a user would see. Contrast that with using a CSS selector to select the image (which you can do, it's just frowned upon) which is very implementation-centric (a user doesn't know nor care about CSS classes.) | ||
|
||
Let's add another test to make sure that we have a default image if pizza isn't passed an image. | ||
|
||
> 🚨 This doesn't work yet. That's intentional. | ||
```javascript | ||
test("to have default image if none is provided", async () => { | ||
const screen = render( | ||
<Pizza name={"Cool Pizza"} description="super cool pizza" /> | ||
); | ||
|
||
const img = screen.getByRole("img"); | ||
expect(img.src).not.toBe(""); | ||
}); | ||
``` | ||
|
||
Well, first thing it is going to complain about is that we don't clean up after ourselves after each test. React Testing Library requires us to. | ||
|
||
```javascript | ||
// at top | ||
import { render, cleanup } from "@testing-library/react"; // add cleanup | ||
import { afterEach, expect, test } from "vitest"; // add afterEach | ||
afterEach(cleanup); | ||
``` | ||
|
||
The test still doesn't pass? Oh, that's because it caught a bug! If you don't give it a image, it just breaks. That's not a good user experience. Let's go fix it in Pizza.jsx. | ||
|
||
```javascript | ||
// replace src | ||
src={props.image ? props.image : "https://picsum.photos/200"} | ||
``` | ||
|
||
Now it works! | ||
|
||
Bam! Some easy React testing there for you. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
Let's test the Contact page. It has a nice, simple user interaction. User puts their information in, we submit to the API, and we show them a submitted header. Let's test all of that. | ||
|
||
First we need a helper function, vitest-fetch-mock. We don't _need_ it because Vitest can do it, but I think it just makes things easier. | ||
|
||
```bash | ||
npm i -D [email protected] | ||
``` | ||
|
||
And now let's write the code | ||
|
||
```javascript | ||
import { render } from "@testing-library/react"; | ||
import { expect, test, vi } from "vitest"; | ||
import createFetchMock from "vitest-fetch-mock"; | ||
import { QueryClientProvider, QueryClient } from "@tanstack/react-query"; | ||
import { Route } from "../routes/contact.lazy"; | ||
|
||
const queryClient = new QueryClient(); | ||
|
||
const fetchMocker = createFetchMock(vi); | ||
fetchMocker.enableMocks(); | ||
|
||
test("can submit contact form", async () => { | ||
fetchMocker.mockResponse(JSON.stringify({ status: "ok" })); | ||
const screen = render( | ||
<QueryClientProvider client={queryClient}> | ||
<Route.options.component /> | ||
</QueryClientProvider> | ||
); | ||
|
||
const nameInput = screen.getByPlaceholderText("Name"); | ||
const emailInput = screen.getByPlaceholderText("Email"); | ||
const msgTextArea = screen.getByPlaceholderText("Message"); | ||
|
||
const testData = { | ||
name: "Brian", | ||
email: "[email protected]", | ||
message: "This is a test message", | ||
}; | ||
|
||
nameInput.value = testData.name; | ||
emailInput.value = testData.email; | ||
msgTextArea.value = testData.message; | ||
|
||
const btn = screen.getByRole("button"); | ||
|
||
btn.click(); | ||
|
||
const h3 = await screen.findByRole("heading", { level: 3 }); | ||
|
||
expect(h3.innerText).toContain("Submitted"); | ||
|
||
const requests = fetchMocker.requests(); | ||
expect(requests.length).toBe(1); | ||
expect(requests[0].url).toBe("/api/contact"); | ||
expect(fetchMocker).toHaveBeenCalledWith("/api/contact", { | ||
body: JSON.stringify(testData), | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
method: "POST", | ||
}); | ||
}); | ||
``` | ||
|
||
- We need to wrap React Query with a query provider always or it doesn't work. In this case we can just make that part of the initial render. | ||
- You can pre-populate React Query with a valid cache and then test if your app uses that correctly. Totally valid way to test. In this case we want to make sure our endpoint is being posted to correctly (to make sure we record a contact)so we aren't doing that. | ||
- `vi` is Vitest's spy library, similar to sinon. | ||
- vitest-fetch-mock is just a nice layer on top of vi. We could just use vi directly. | ||
- Notice we can just call `click` and it does all the things we'd expect. Cool, right? | ||
- We then make some assertions that our h3 shows up correctly, that our API was called once, and that it was submitted to our API the way we expect it to. This may seem a bit implementation-oriented, and it is, but typically in a larger app we'd have a library for calling our API and we'd test that individually but we don't here so we're wrapping it all together. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
--- | ||
description: "" | ||
--- | ||
|
||
# TODO – this is to be rewritten | ||
|
||
This is meant to be a very brief treatise on how to do testing on React applications. This will be a brief intro on how to set up Vitest tests for the application we just created. | ||
|
||
## Testing with Vitest | ||
|
||
[Vitest][vitest] is a test runner made by the fine folks who make Vite (as well as Vue.) The idea behind Vitest is that you already have a complete build pipeline for making an app, why should that pipeline be any different for test? It shouldn't; you want your testing environment to look as much like your app environment as possible. | ||
|
||
They designed it to be a drop-in replacement for [Jest][jest] which is what I have taught for this course since the beginning. Jest is great and still a very viable tool to use for testing, even with Vite. We're just going to use Vitest because 1. we don't have to do any more configuration and 2. 100% of what you will learn in here is going to be useful if you use Jest. Win-win. If you want to learn Jest specifically, [take a look at Intermediate React v4's testing section.][v4] | ||
|
||
> Fun side note: [Jest is now an OpenJS project and no longer directly under Facebook][fb]. | ||
While Vitest is not using Jasmine directly, its APIs mimic Jasmine APIs (just like Jest.) | ||
|
||
Let's get going Run `npm install -D [email protected] @vitest/[email protected] [email protected] [email protected]`. | ||
|
||
> This course previously taught [@testing-library/react][tlr] and used a synthetic DOM tool called [happy-dom][hd]. Vitest now has a first class integration with [Playwright][playwright] and their own version @testing-library/react, vitest-browser-react. Hence this version will use those tools. If you want to learn those other tools, the [v8][v8] version of this course all still works. | ||
We are going to be using the latest-and-greatest testing tool from Microsoft called Playwright, the spirtual successor to Google's Puppeteer tool. Using this tool, your command line client will spin up a _real_ copy of Webkit, Firefox, or Chromium and run your tests inside of them. Can't beat the real thing, and it's finally fast and not-flaky enough for it to be worth it! | ||
|
||
Okay, let's go set up Vitest to work with our Vite project. In your `vite.config.js` | ||
|
||
```javascript | ||
|
||
// under server | ||
test: { | ||
setupFiles: ["vitest-browser-react"], | ||
browser: { | ||
enabled: true, | ||
name: "firefox", // or chromium or webkit | ||
provider: "playwright", | ||
}, | ||
}, | ||
``` | ||
|
||
If you've never run Playwright before, you should run `npx playwright install` to install all the browser stuff necessary. | ||
|
||
Let's go add an npm script. In your package.json. | ||
|
||
```json | ||
"test": "vitest" | ||
``` | ||
|
||
> Fun trick: if you call it test, npm lets you run that command as just `npm t`. | ||
This command let's you run Vitest in an interactive mode where it will re-run tests selectively as you save them. This lets you get instant feedback if your test is working or not. This is probably my favorite feature of Vitest. So try it now. It should error because we have no tests, but it should open a browser with the testing harness loaded. | ||
|
||
```bash | ||
# these all do the same thing | ||
npm run test | ||
npm test | ||
npm t | ||
``` | ||
|
||
Also good to go install the [Vitest VS Code extension][vitest-vsc] if you're using VS Code. | ||
|
||
Now that we've got that going, let's go write a test. | ||
|
||
[vitest-vsc]: https://marketplace.visualstudio.com/items?itemName=vitest.explorer | ||
[jest]: https://jestjs.io | ||
[jasmine]: https://jasmine.github.io/ | ||
[enzyme]: http://airbnb.io/enzyme/ | ||
[res]: https://raw.githubusercontent.com/btholt/complete-intro-to-react-v5/testing/__mocks__/@frontendmasters/res.json | ||
[fb]: https://twitter.com/cpojer/status/1524419433938046977 | ||
[hd]: https://github.com/capricorn86/happy-dom | ||
[vitest]: https://vitest.dev/ | ||
[v4]: https://frontendmasters.com/courses/intermediate-react-v4/setup-jest-testing-library/ | ||
[tlr]: https://github.com/testing-library/react-testing-library | ||
[v8]: https://frontendmasters.com/courses/intermediate-react-v5/setup-react-testing-library-vitest/ | ||
[playwright]: https://playwright.dev/ |
9 changes: 1 addition & 8 deletions
9
...07-testing/B-the-most-basic-react-test.md → lessons/07-testing/F-browser-tests.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters