-
Notifications
You must be signed in to change notification settings - Fork 727
docs: add marko testing library #159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,177 @@ | ||||||||
--- | ||||||||
id: api | ||||||||
title: API | ||||||||
--- | ||||||||
|
||||||||
`Marko Testing Library` re-exports everything from `DOM Testing Library` as well | ||||||||
as these methods: | ||||||||
|
||||||||
- [`render`](#render) | ||||||||
- [`cleanup`](#cleanup) | ||||||||
|
||||||||
--- | ||||||||
|
||||||||
## `render` | ||||||||
|
||||||||
```javascript | ||||||||
function render( | ||||||||
template, // A Marko template to render | ||||||||
input, // Input for the above template | ||||||||
options // You won't often use this, expand below for docs on options | ||||||||
): | ||||||||
``` | ||||||||
|
||||||||
Render into a container which is appended to `document.body`. | ||||||||
|
||||||||
```javascript | ||||||||
import { render } from '@marko/testing-library' | ||||||||
import MyTemplate from './my-template.marko' | ||||||||
|
||||||||
render(MyTemplate) | ||||||||
``` | ||||||||
|
||||||||
```javascript | ||||||||
import { render, cleanup } from '@marko/testing-library' | ||||||||
import 'jest-dom/extend-expect' | ||||||||
import Greeting from './greeting.marko' | ||||||||
|
||||||||
afterEach(cleanup) | ||||||||
|
||||||||
test('renders a message', async () => { | ||||||||
const { container, getByText } = await render(Greeting, { name: 'Marko' }) | ||||||||
expect(getByText(/Marko/)).toBeInTheDocument() | ||||||||
expect(container.firstChild).toMatchInlineSnapshot(` | ||||||||
<h1>Hello, Marko!</h1> | ||||||||
`) | ||||||||
}) | ||||||||
``` | ||||||||
|
||||||||
> Note | ||||||||
> | ||||||||
> The [cleanup](#cleanup) function should be called between tests to remove the | ||||||||
> created DOM nodes and keep the tests isolated. | ||||||||
|
||||||||
## `render` Options | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Docs looks great and solid! 👍 It's a bit weird, thou, that the "render options" section uses the same heading as the parent "render" heading. I'd suggest setting "render Options" as a third-level heading, and "container" as a fourth-level one. Same thing with "render result" and its children. 😃
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Excellent catches! Thanks for the review! 😄 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I realized I missed this part:
I think having those as a higher heading is still useful as it causes it to show up in the side nav (and these are probably the most important parts of the lib). This is also what the
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Totally right, I was thinking about that while typing, actually 🤔 Let's follow RTL example, then! If we ever feel the urge to change it we can make sure we update each framework docs :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I still like moving In my last commit I updated the PR to do as I said above, but I left the I think I'm happy with it now 😄 |
||||||||
|
||||||||
You won't often need to specify options, but if you ever do, here are the | ||||||||
available options which you could provide as the third argument to `render`. | ||||||||
|
||||||||
### `container` | ||||||||
|
||||||||
By default for client side tests, `Marko Testing Library` will create a `div` | ||||||||
and append that `div` to the `document.body` and this is where your component | ||||||||
will be rendered. If you provide your own HTMLElement `container` via this | ||||||||
option, it will not be appended to the `document.body` automatically. | ||||||||
|
||||||||
For example: If you are unit testing a `tablebody` element, it cannot be a child | ||||||||
of a `div`. In this case, you can specify a `table` as the render `container`. | ||||||||
|
||||||||
```javascript | ||||||||
const table = document.createElement('table') | ||||||||
|
||||||||
const { container } = await render(MyTableBody, null, { | ||||||||
container: document.body.appendChild(table), | ||||||||
}) | ||||||||
``` | ||||||||
|
||||||||
## `render` Result | ||||||||
|
||||||||
The `render` method returns a promise which resolves with an object that has a | ||||||||
few properties: | ||||||||
|
||||||||
### `...queries` | ||||||||
|
||||||||
The most important feature of `render` is that the queries from | ||||||||
[DOM Testing Library](dom-testing-library/api-queries.md) are automatically | ||||||||
returned with their first argument bound to the results of rendering your | ||||||||
component. | ||||||||
|
||||||||
See [Queries](dom-testing-library/api-queries.md) for a complete list. | ||||||||
|
||||||||
**Example** | ||||||||
|
||||||||
```javascript | ||||||||
const { getByLabelText, queryAllByTestId } = await render(MyTemplate) | ||||||||
``` | ||||||||
|
||||||||
### `debug` | ||||||||
|
||||||||
This method is a shortcut for logging the `prettyDOM` for all children inside of | ||||||||
the `container`. | ||||||||
|
||||||||
```javascript | ||||||||
import { render } from '@marko/testing-library' | ||||||||
import Greeting from './greeting.marko' | ||||||||
|
||||||||
const { debug } = await render(Greeting, { name: 'World' }) | ||||||||
debug() | ||||||||
|
||||||||
// <h1>Hello World</h1> | ||||||||
// you can also pass an element: debug(getByTestId('messages')) | ||||||||
``` | ||||||||
|
||||||||
This is a simple wrapper around `prettyDOM` which is also exposed and comes from | ||||||||
[`DOM Testing Library`](https://github.com/testing-library/dom-testing-library/blob/master/README.md#prettydom). | ||||||||
|
||||||||
### `rerender` | ||||||||
|
||||||||
It'd probably be better if you test the component that's doing the prop updating | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this could be worded a bit more conservatively - rerender behavior is a valid thing to test for reusable components There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @alexkrolick I agree. This is a remnant of basing the docs off of the react ones. Let me try to word smith this better. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated this section of the docs, let me know what you think 😄 |
||||||||
to ensure that the props are being updated correctly (see | ||||||||
[the Guiding Principles section](guiding-principles.md)). That said, if you'd | ||||||||
prefer to update the props of a rendered component in your test, this function | ||||||||
can be used to update props of the rendered component. | ||||||||
|
||||||||
```javascript | ||||||||
import { render } from '@marko/testing-library' | ||||||||
import Greeting from './greeting.marko' | ||||||||
|
||||||||
const { rerender, debug } = await render(Greeting, { name: 'World' }) | ||||||||
|
||||||||
// re-render the same component with different props | ||||||||
await rerender({ name: 'Marko' }) | ||||||||
|
||||||||
debug() | ||||||||
// <h1>Hello Marko</h1> | ||||||||
``` | ||||||||
|
||||||||
### `container` | ||||||||
|
||||||||
The containing DOM node of your rendered Marko Component. For server side tests | ||||||||
this is a [JSDOM.fragment](), and for client side tests this will be whatever is | ||||||||
passed as the `container` render option. | ||||||||
|
||||||||
> Tip: To get the root element of your rendered element, use | ||||||||
> `container.firstChild`. | ||||||||
|
||||||||
> 🚨 If you find yourself using `container` to query for rendered elements then | ||||||||
> you should reconsider! The other queries are designed to be more resilient to | ||||||||
> changes that will be made to the component you're testing. Avoid using | ||||||||
> `container` to query for elements! | ||||||||
|
||||||||
## `cleanup` | ||||||||
|
||||||||
With client side tests your components are rendered into a placeholder | ||||||||
HTMLElement. To ensure that your components are properly removed, and destroyed, | ||||||||
you can call `cleanup` at any time which will remove any attached components. | ||||||||
|
||||||||
```javascript | ||||||||
import { cleanup } from '@marko/testing-library' | ||||||||
// automatically unmount and cleanup DOM after the test is finished. | ||||||||
afterEach(cleanup) | ||||||||
``` | ||||||||
|
||||||||
To save some typing, you could also import a file with the above. | ||||||||
|
||||||||
```javascript | ||||||||
import '@marko/testing-library/cleanup-after-each' | ||||||||
``` | ||||||||
|
||||||||
If you are using Jest, you can simply include the following to your Jest config | ||||||||
to avoid doing this in each file. | ||||||||
|
||||||||
```javascript | ||||||||
module.exports = { | ||||||||
..., | ||||||||
setupFilesAfterEnv: ['@marko/testing-library/cleanup-after-each'] | ||||||||
} | ||||||||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
--- | ||
id: intro | ||
title: Marko Testing Library | ||
sidebar_label: Introduction | ||
--- | ||
|
||
The [`Marko Testing Library`](gh) is a very lightweight solution for testing | ||
Marko components. It provides light utility functions on top of | ||
[`@testing-library/dom`](https://github.com/testing-library/dom-testing-library) | ||
in a way that encourages better testing practices. | ||
|
||
``` | ||
npm install --save-dev @marko/testing-library | ||
``` | ||
|
||
- [Marko Testing Library on GitHub][gh] | ||
|
||
[gh]: https://github.com/marko-js/testing-library | ||
|
||
## The problem | ||
|
||
You want to write maintainable tests for your Marko components. As a part of | ||
this goal, you want your tests to avoid including implementation details of your | ||
components and rather focus on making your tests give you the confidence for | ||
which they are intended. As part of this, you want your testbase 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 `@marko/testing-library` is a very lightweight solution for testing Marko | ||
components. It provides light utility functions on top of | ||
[`@testing-library/dom`](https://github.com/testing-library/dom-testing-library) | ||
in a way that encourages better testing practices. Its primary guiding principle | ||
is: | ||
|
||
> [The more your tests resemble the way your software is used, the more confidence they can give you.](#guiding-principles) | ||
|
||
So rather than dealing with instances of rendered Marko components, your tests | ||
will work with actual DOM nodes. The utilities this library provides facilitate | ||
querying the DOM in the same way the user would. Finding for elements by their | ||
label text (just like a user would), finding links and buttons from their text | ||
(like a user would). It contains a small targeted API and can get out of your | ||
way if absolutely needed with some built in escape hatches. | ||
|
||
This library encourages your applications to be more accessible and allows you | ||
to get your tests closer to using your components the way a user will, which | ||
allows your tests to give you more confidence that your application will work | ||
when a real user uses it. | ||
|
||
**What this library is not**: | ||
|
||
1. A test runner or framework | ||
2. Specific to a testing framework, you can [use it with Jest](./setup#jest), | ||
[mocha](./setup#mocha), or other test runners. | ||
|
||
> NOTE: This library is built on top of | ||
> [`DOM Testing Library`](dom-testing-library/intro.md) which is where most of | ||
> the logic behind the queries is. |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,105 @@ | ||||||
--- | ||||||
id: setup | ||||||
title: Setup | ||||||
sidebar_label: Setup | ||||||
--- | ||||||
|
||||||
`Marko Testing Library` is not dependent on any test runner, however it is | ||||||
dependent on the test environment. These utilities work for testing both server | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What are "these utilities" in this paragraph? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does that mean you can only test built files and not individual components? AFAIK most tests that require from the src folder aren't really aware of the packaging system. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can test individual components, the key is that by default the server side version of the component is resolved. For a client side version of a component to be resolved, the bundler, or test runner, needs to be aware of the browser field. In Marko components are compiled to an optimized SSR streaming API or a VDOM api. The browser field is used to provide an appropriate runtime based on the environment. |
||||||
side, and client side Marko templates and provide a slightly different | ||||||
implementation for each. This is done using a | ||||||
[browser shim](https://github.com/defunctzombie/package-browser-field-spec), | ||||||
just like in Marko. | ||||||
|
||||||
The [browser shim](https://github.com/defunctzombie/package-browser-field-spec) | ||||||
is picked up by many tools, including all bundlers and some test runners. | ||||||
|
||||||
Below is some example configurations to test both server and browser components | ||||||
with some popular test runners. | ||||||
|
||||||
### [Jest](http://jestjs.io) | ||||||
|
||||||
For Jest to understand Marko templates you must first | ||||||
[install the @marko/jest preset](https://github.com/marko-js/jest#installation). | ||||||
This allows your Marko templates to be imported into your tests. | ||||||
|
||||||
In Jest there is a | ||||||
[browser option](https://jestjs.io/docs/en/configuration#browser-boolean) which | ||||||
will tell Jest to resolve the | ||||||
[browser shim](https://github.com/defunctzombie/package-browser-field-spec) | ||||||
version of all modules as mentioned above. | ||||||
|
||||||
To test components rendered in the client side, be sure to enable both the | ||||||
`browser` option and the preset and you are good to go! | ||||||
|
||||||
**jest.config.js** | ||||||
|
||||||
```javascript | ||||||
module.exports = { | ||||||
preset: '@marko/jest', | ||||||
browser: true, // Tells Jest to resolve browser shims. | ||||||
} | ||||||
``` | ||||||
|
||||||
For testing components rendered server side we can omit the `browser` option, | ||||||
however ideally you should also set the | ||||||
[`testEnvironment option`](https://jestjs.io/docs/en/configuration#testenvironment-string) | ||||||
to `node` which will disable loading JSDOM globally. | ||||||
|
||||||
**jest.config.js** | ||||||
|
||||||
```javascript | ||||||
module.exports = { | ||||||
preset: '@marko/jest', | ||||||
testEnvironment: 'node', // Tells Jest not to load a global JSDOM for server side. | ||||||
} | ||||||
``` | ||||||
|
||||||
A Jest configuration can also have multiple | ||||||
[projects](https://jestjs.io/docs/en/configuration#projects-array-string-projectconfig) | ||||||
which we can use to create a combined configuration for server side tests, and | ||||||
browser side tests, like so: | ||||||
|
||||||
**jest.config.js** | ||||||
|
||||||
```javascript | ||||||
module.exports = { | ||||||
projects: [ | ||||||
{ | ||||||
displayName: 'server', | ||||||
testEnvironment: 'node', | ||||||
preset: '@marko/jest', | ||||||
testRegex: '/__tests__/[^.]+\\.server\\.js$', | ||||||
}, | ||||||
{ | ||||||
displayName: 'browser', | ||||||
preset: '@marko/jest', | ||||||
browser: true, | ||||||
testRegex: '/__tests__/[^.]+\\.browser\\.js$', | ||||||
}, | ||||||
], | ||||||
} | ||||||
``` | ||||||
|
||||||
### [Mocha](https://mochajs.org) | ||||||
|
||||||
Mocha also works great for testing Marko components. Mocha, however, has no | ||||||
understanding of | ||||||
[browser shims](https://github.com/defunctzombie/package-browser-field-spec) | ||||||
which means out of the box it can only work with server side Marko components. | ||||||
|
||||||
To run server side Marko tests with `mocha` you can simply run the following | ||||||
command: | ||||||
|
||||||
```console | ||||||
mocha -r marko/node-require | ||||||
``` | ||||||
|
||||||
This enables the | ||||||
[Marko require hook](https://markojs.com/docs/installing/#require-marko-views) | ||||||
and allows you to require server side Marko templates directly in your tests. | ||||||
|
||||||
For client side testing of your components with Mocha often you will use a | ||||||
bundler to build your tests (this will properly resolve the browser shims | ||||||
mentioned above) and then you can load these tests in some kind of browser | ||||||
context. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.