When it comes to testing visual components, first adivice is: don’t do it - rather test store/actions/reducers/validation methods (i.e. pure functions). But if you really want (or must), here’s a method with VueJS I found the most helpful of several: using Vue test-utils’ querySelector-like API to extract content (this assumes Vue3 with Vite).

There’s several alternatives to testing visual components:

  • end2end tests - which are notoriously flaky and hard to keep stable (and not a unit test)
  • rendering (deep) - in any shape or form not a unit test (with all problems coming with it)
  • rendering (shallow) the component to HTML and just using regular expressions
  • rendering (shallow) the component to HTML and using e.g. unexpected-dom (or JSDOM)
  • rendering (shallow) the component and using plain selector methods for text or a small subset of HTML

I found the last one the most effective - as it’s the minimal, most straightforward solution. And the querySelector-like syntax works for most actual scenarios. Plus we can still use findComponent to check props of children.

What’s needed

  • Vue - I’m using Vite along with Vitest (could also use Jest et al - both frameworks are super-similar)
  • Vue test-utils

The Basics

Vue test-utils allows rendering a component with given props to HTML. I do prefer shallow rendering, i.e. sub-components just appear as <sub-component></sub-component> and not rendered. This keeps the scope of tests small enough. Here’s how that works - given the following rendered example:

<div>
  <table>
    <tbody>
      <tr>
        <td>
          Test-Sth
          <small>subtext</small>
        </td>
        <td>
          Sth else
        </td>
      </tr>
    </tbody>
  </table>
  <my-sub></my-sub>
</div>

Now w/ in any test method (it('what', () => { /* do some testing */})), one can use:

const wrapper = mount(MyComp, { shallow: true, props: {myprop: 'myvalue'} });
expect(wrapper.find('table td:nth-child(1)').text()).toMatch(/Test-Sth.*subtext/);
expect(wrapper.find('table td:nth-child(1)').html()).toMatch(/Test-Sh.*<small.*subtext<\/small>/);    

So it’s possible to drill down to what we really want to test with everything CSS has got (and that is a lot) - and then write specific tests.

Mocking stores et al

Components might require (or import) things, esp. stores (which are great) - and we can mock those out in the usual way (vitest and jest are pretty similar here, as well):

vi.mock('../../stores/my-store', () => {
  const useMyStore: any = () => useMyStore; // return oneself - also allow access to mock functions in later assertions
  useMyStore.loadSth = vi.fn();
  useMyStore.sth = [{id: 42, title: 'Test-Sth', /*...*/}]
  return { useMyStore };
});

Now, the component will get a mock instead of the real dependency for the scope of the test. As we’re using JS’ duckt-typing, we can even drill down into objects returned from constructor functions (like defineStore is).

Assuming the component needs to call loadSth on mounted, we can now test whether the invocation happened:

expect((useMyStore as any).loadOwned).toHaveBeenCalled(); // (no params on this one, but you get the idea)

Checking sub-components

We can also use findComponent (see API Reference) et al to check whether sub-components get the right properties. Here’s an example:

const mySub = wrapper.findComponent(MySub);
expect(mySub.props('foo')?.bar).toBe(42);

nextTick for computed et al

If we have computed props and similar, calling

await wrapper.vm.$nextTick();

might make a ton of sense as we give Vue a chance to compute and poss. re-render all we have.

So…

…even though it’s only second preferennce, there is a quite pragmatic approach to testing single components in the Vue ecosystem.

Hope you found this useful - as ever: let me know what you think!