Skip to content

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

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 177 additions & 0 deletions docs/marko-testing-library/api.md
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

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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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']
}
```
60 changes: 60 additions & 0 deletions docs/marko-testing-library/intro.md
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.
105 changes: 105 additions & 0 deletions docs/marko-testing-library/setup.md
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
dependent on the test environment. These utilities work for testing both server
dependent on browser globals. These utilities work for testing both server

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are "these utilities" in this paragraph?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe test environment is not the most clear here. Basically I am trying to express that if your tooling does not pick up on the browser field in the package.json then your tests will be running in the SSR mode. For jest this just means enabling the browser option. Just having a global JSDOM for example (as is the default for jest) will not work by itself. It's a bit of a strange thing to explain to new comers though, do you have some thoughts on how to better describe this?

these utilities really just mean the api's exposed by the package. Maybe these apis would be better?

Copy link
Collaborator

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.
9 changes: 9 additions & 0 deletions website/sidebars.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@
"vue-testing-library/faq"
]
},
{
"type": "subcategory",
"label": "Marko Testing Library",
"ids": [
"marko-testing-library/intro",
"marko-testing-library/setup",
"marko-testing-library/api"
]
},
"cypress-testing-library/intro",
"svelte-testing-library/intro",
"angular-testing-library/intro",
Expand Down