Skip to content

docs: shallow and stubs #38

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 4 commits into from
Jul 17, 2020
Merged
Changes from 1 commit
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
199 changes: 198 additions & 1 deletion src/guide/stubs-shallow-mount.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,200 @@
# Stubs and Shallow Mount

Mostly `mount({ shallow: true })` and `global.stubs`.
Vue Test Utils provides some features for *stubbing* components. A *stub* is where you replace an existing implementation of a custom component with a dummy component that doesn't do anything at all, which can simplify an otherwise complex test. Let's see an example.

## Mocking with `global.stubs`
Copy link
Member

Choose a reason for hiding this comment

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

💡 I'd suggest using headings that help readers understand what's the section about over using API-focused ones

Suggested change
## Mocking with `global.stubs`
## Stubbing a single child component


A common example is when you would like to test something in a component that appers very high in the component hierarchy.

In this example, we have an `<App>` that renders a message, as well as a `FetchDataFromApi` component that makes an API call.

```js
const FetchDataFromApi = {
name: 'FetchDataFromApi',
template: `
<div>{{ result }}</div>
`,
async mounted() {
const res = await axios.get('/api/info')
this.result = res.data
},
data() {
return {
result: ''
}
}
}

const App = {
components: {
FetchDataFromApi
},
template: `
<div>
<h1>Welcome to Vue.js 3</h1>
<fetch-data-from-api />
</div>
`
}
```

We do not want to make the API call in this particular test, we just want to assert the message is rendered. In this case, we could use the `stubs`, which appears in the `global` mounting option.

```js {12}
test('stubs', () => {
const wrapper = mount(App, {
global: {
stubs: {
FetchDataFromApi: {
template: '<span />'
}
}
}
})

console.log(wrapper.html()) // <div><h1>Welcome to Vue.js 3</h1><span></span></div>
expect(wrapper.html()).toContain('Welcome to Vue.js 3')
})
```

Notice that the template is showing `<span></span>` where `<fetch-data-from-api />` was? We replaced it with a stub - in this case, we provided our own implementation by passing in a `template`.

You can also get a default stub, instead of providing your own:

```js {9}
test('stubs', () => {
const wrapper = mount(App, {
global: {
stubs: {
FetchDataFromApi: true
}
}
})
console.log(wrapper.html()) // <div><h1>Welcome to Vue.js 3</h1><fetch-data-from-api-stub></fetch-data-from-api-stub></div>
Copy link
Member

Choose a reason for hiding this comment

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

(same suggestion as above!)

Copy link
Member Author

@lmiller1990 lmiller1990 Jul 17, 2020

Choose a reason for hiding this comment

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

Hm. I don't want people to think asserting a that a specific dom structure is rendered is a good idea, though. 🤔

expect(wrapper.html()).toContain('Welcome to Vue.js 3')
})
```

This will stub out *all* the `<FetchDataFromApi />` components in the entire render tree, regardless of what level they appear at. That's why it is in the `global` mounting option.
Copy link
Member

Choose a reason for hiding this comment

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

nice point


## `shallow: true` or `shallowMount`
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
## `shallow: true` or `shallowMount`
## Stubbing all children components

Copy link
Member Author

Choose a reason for hiding this comment

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

Great!


Sometimes you might want to stub out *all* the custom components. For example you might have a component like this:
Copy link
Member

Choose a reason for hiding this comment

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

I love framing shallowMount this way 👌


```js
const ComplexComponent = {
components: { ComplexA, ComplexB, ComplexC },
template: `
<div>
<h1>Welcome to Vue.js 3</h1>
<ComplexA />
<ComplexB />
<ComplexC />
</div>
`
}
```

Imagine each of the `<Complex>` does something complicated, and you are only interested in testing that the `<h1>` is rendering the correct greeting. You could do something like:

```js
const wrapper = mount(ComplexComponent, {
global: {
stubs: {
ComplexA: true,
ComplexB: true,
ComplexC: true,
}
}
})
```

But that's a lot of boilerplate. VTU has a `shallow` mounting option that will automatically stub out all the child components:

```js {5}
test('shallow', () => {
const wrapper = shallow(ComplexComponent, {
shallow: true
})
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
const wrapper = shallow(ComplexComponent, {
shallow: true
})
const wrapper = mount(ComplexComponent, {
shallow: true
})

Copy link
Member Author

Choose a reason for hiding this comment

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

Derp

console.log(wrapper.html()) // <div><h1>Welcome to Vue.js 3</h1><complex-a-stub></complex-a-stub><complex-b-stub></complex-b-stub><complex-c-stub></complex-c-stub></div>
})
```

> TIP: If you used VTU V1, you may remember this as `shallowMount`. That method is still available, too - it's the same as writing `shallow: true`.

## Default Slots and `shallow`

Since `shallow` stubs out all the render function of any custom components, any `<slot>` components are not rendered when using `shallow`. While this is not a problem in most cases, there are some scenarios where this isn't ideal. Maybe you have a custom button component in your app:
Copy link
Member

Choose a reason for hiding this comment

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

simplified wording a bit

Suggested change
Since `shallow` stubs out all the render function of any custom components, any `<slot>` components are not rendered when using `shallow`. While this is not a problem in most cases, there are some scenarios where this isn't ideal. Maybe you have a custom button component in your app:
Since `shallow` stubs out all the content of a components, any `<slot>` won't get rendered when using `shallow`. While this is not a problem in most cases, there are some scenarios where this isn't ideal.
Let's say you have a custom button component in your app:


```js
const CustomButton = {
template: `
<button>
<slot />
</button>
`
}
```

And you might use it like this:

```js
const App = {
props: ['authenticated'],
components: { CustomButton },
template: `
<div>
<h1>Welcome!</h1>
<custom-button>
<div v-if="authenticated">
Log out
</div>
<div v-else>
Log in
</div>
</custom-button>
</div>
Copy link
Member

Choose a reason for hiding this comment

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

Since heading is not related to the text, we might as well remove it and make it a bit terser. Also I feel inlining makes it easier to read, but thats just my opinion :) feel free to ignore it

Suggested change
<div>
<h1>Welcome!</h1>
<custom-button>
<div v-if="authenticated">
Log out
</div>
<div v-else>
Log in
</div>
</custom-button>
</div>
<custom-button>
<div v-if="authenticated">Log out</div>
<div v-else>Log in</div>
</custom-button>

`
}
```

If you are using `shallow`, the `<slot />` will not be rendered, since the render function in `<custom-button />` is stubbed out. That means you won't be able to verify the correct text is rendered! For this use case, you can use `config.renderDefaultStub`, which will render the default `<slot />`, even when using `shallow`:

```js {1,4,8}
import { config, mount } from '@vue/test-utils'

beforeAll(() => {
config.renderStubDefaultSlot = true
})

afterAll(() => {
config.renderStubDefaultSlot = false
})

test('shallow with stubs', () => {
const wrapper = mount(AnotherApp, {
props: {
authenticated: true
},
shallow: true
})

expect(wrapper.html()).toContain('Log out')
})
```

Since this behavior is global, not on a `mount` by `mount` basis, you need to remember to enable/disable it before and after each test.

If you prefer this behavior, you can enable this globally by importing `config` in your test setup file, and setting `renderStubDefaultSlot` to `true`. Unfortunately, due to technical limitations, this behavior is not extended to slots other than the default slot.
Copy link
Member

Choose a reason for hiding this comment

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

Unfortunately, due to technical limitations, this behavior is not extended to slots other than the default slot.

I feel this sentence is important enough to give it more prominence. Maybe using a ::: tip or ::: info banner?

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree, this seems like a big deal for some users


## `mount`, `shallow` and `stubs`: which one and when?
Copy link
Member

Choose a reason for hiding this comment

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

Great section ❤️


Tests that use `mount` will render the entire component hierarchy, which is closer to what the user will experiene in a real browser. Tests using `shallow` are focused on a specific component. As a rule of thumb, the more your tests resemble the way your software is used, the more confidence they can give you. `shallow` can be useful for testing a specific component in complete isolation. If you just have one or two components that are not relevant to your tests, consider using `mount` in combination with `stubs` instead of `shallow`. The more you stub, the less production-like your test becomes.

Keep in mind that whether you are doing a full mount or a shallow render, good tests focus on inputs (`props` and user interaction, such as with `trigger`) and outputs (in the case of a component, the DOM elements that are rendered), not implementation details. So regardless of which mounting method you choose, keep these guidelines in mind.

## Conclusion

- use `global.stubs` to replace a component with a dummy one that does nothing to simplify your tests
- use `shallow: true` (or `shallowMount`) to stub out all custom components
- use `config.stubRenderDefaultSlot` to render the default `<slot>` for a stubbed component