Skip to content

Latest commit

 

History

History
200 lines (161 loc) · 6.59 KB

stubs-shallow-mount.md

File metadata and controls

200 lines (161 loc) · 6.59 KB

Stubs and Shallow Mount

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

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.

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.

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:

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

shallow: true or shallowMount

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

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:

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:

test('shallow', () => {
  const wrapper = shallow(ComplexComponent, {
    shallow: true
  })
  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:

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

And you might use it like this:

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>
  `
}

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:

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.

mount, shallow and stubs: which one and when?

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