test-utils: TypeScript error when using data/props mounting option

For those coming from Google/elsewhere: If you have some problems with typing, for example when writing tests with ts-jest and using SFC (.vue) files, the work-around is to as as any. Eg:

const wrapper = mount(Comp, {
  data() {
    return { foo: 'bar' }
  }
} as any)

Or try this shim:

declare module '*.vue' {
  import { DefineComponent } from 'vue';
  const component: DefineComponent;
  export default component;
}

In the future we aim to have type inference with vue files too. For now, you may need to use as any to avoid compilation errors in your tests.


Original issue:

No idea what’s going on.

Some days I wonder if TS is actually making the DX better or worse, lol.

import { mount } from '@vue/test-utils'

const Layout = {
  template: `
    <div>
      <header>
        <slot name="header" />
      </header>

      <main>
        <slot name="main" />
      </main>
      <footer>
        <slot name="footer" />
      </footer>
    </div>
  `
}

test('layout full page layout', () => {
  const wrapper = mount(Layout, {
    slots: {
      header: '<div>Header</div>',
      main: '<div>Main Content</div>' ,
      footer: {
        template: '<div>Footer</div>'
      }
    }
  })

  expect(wrapper.html()).toContain('Main Content')
})

image

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 37 (16 by maintainers)

Commits related to this issue

Most upvoted comments

@Mister-Hope Can you try to use the following shim:

declare module '*.vue' {
  import { DefineComponent } from 'vue';
  const component: DefineComponent;
  export default component;
}

You have a fairly basic use-case, that we cover in the tests of VTU, so it should be working 🤞

I have type issues too.

shims-vue.d.ts:

declare module "*.vue" {
  import { ComponentOptions } from "vue";
  const comp: ComponentOptions;
  export default comp;
}

Hello world:

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
  </div>
</template>

<script lang="ts">
export default {
  name: "HelloWorld",
  props: {
    msg: { type: String, default: "Welcome to Your Vue.js App" },
  },
};
</script>

Test files:

import { shallowMount } from "@vue/test-utils";
import HelloWorld from "@/components/HelloWorld.vue";

describe("HelloWorld.vue", () => {
  it("renders props.msg when passed", () => {
    const msg = "new message";
    const wrapper = shallowMount(HelloWorld, {
      props: { msg },
    });
    expect(wrapper.text()).toMatch(msg);
  });
});

Error:

 FAIL  tests/unit/example.spec.ts
  ● Test suite failed to run
    TypeScript diagnostics (customize using `[jest-config].globals.ts-jest.diagnostics` option):
    tests/unit/example.spec.ts:7:34 - error TS2769: No overload matches this call.
      The last overload gave the following error.
        Argument of type 'ComponentOptions<{}, any, any, any, any, any, any, any>' is not assignable to parameter of type 'ComponentOptionsWithObjectProps<readonly string[] | Readonly<ComponentObjectPropsOptions<Record<string, unknown>>>, any, any, any, any, any, any, any, string, Readonly<...> | Readonly<...>, { ...; } | {}>'.
          Property 'props' is missing in type 'ComponentOptionsBase<{}, any, any, any, any, any, any, any, string, {}> & ThisType<any>' but required in type '{ props: (readonly string[] & ThisType<void>) | (Readonly<ComponentObjectPropsOptions<Record<string, unknown>>> & ThisType<void>); }'.

    7     const wrapper = shallowMount(HelloWorld, {
                                       ~~~~~~~~~~

      node_modules/@vue/runtime-core/dist/runtime-core.d.ts:335:5
        335     props: PropsOptions & ThisType<void>;
                ~~~~~
        'props' is declared here.
      node_modules/@vue/test-utils/dist/mount.d.ts:36:25
        36 export declare function mount<PropsOptions extends Readonly<ComponentPropsOptions>, RawBindings, D, C extends ComputedOptions = {}, M extends Record<string, Function> = {}, E extends EmitsOptions = Record<string, any>, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin, EE extends string = string>(componentOptions: ComponentOptionsWithObjectProps<PropsOptions, RawBindings, D, 
C, M, E, Mixin, Extends, EE>, options?: MountingOptions<ExtractPropTypes<PropsOptions>, D>): VueWrapper<ComponentPublicInstance<ExtractPropTypes<PropsOptions>, RawBindings, D, C, M, E, VNodeProps & ExtractPropTypes<PropsOptions>>>;
                                   ~~~~~
        The last overload is declared here.

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        4.168s
Ran all test suites.

Addtional Info:

Works fine with:

import { shallowMount } from "@vue/test-utils";
import HelloWorld from "@/components/HelloWorld.vue";

describe("HelloWorld.vue", () => {
  it("renders props.msg when passed", () => {
    const msg = "new message";
    const wrapper = shallowMount(HelloWorld as any, {
      props: { msg },
    });
    expect(wrapper.text()).toMatch(msg);
  });
});

When casting HelloWorld to any

Note for people stumbling on this issue from google, improperly formatted mount options can cause issues that look like this (makes you think that the problem is with the component being mounted), and won’t be fixed by the shim solution above.

In my case, I was accidentally passing an array of Vue components to components like:

mount(Component, { global: { components: [Button, etc] } })

instead of the object it expects like

mount(Component, { global: { components: { Button, etc } } })

In my case, similar to what @vincerubinetti said, I had assumed initially that the issue was with how it was being mounted.

it("renders title correctly", () => {
    const buttonText = "Test Button";
    const wrapper = mount(IconButton, {
      props: {
        titleConfig: {
          value: buttonText,
        },
      },
    });
    expect(wrapper.text()).toMatch(buttonText);
  });

Turns out, the type for my prop titleConfig was set to an interface that required two keys to be passed and I was passing only one of them (value). Setting the other key as optional in the interface definition removed this error for me.

@Mister-Hope Can you try to use the following shim:

‘’‘ts declare module ‘*.vue’ { import { DefineComponent } from ‘vue’; const component: DefineComponent; export default component; } ‘’’

You have a fairly basic use-case, that we cover in the tests of VTU, so it should be working 🤞

Hi. This aolution is not working for me 😦 I have the same problem image

@lmiller1990 try to set as MountingOptions.

shallowMount(RecoverPage, {
  global: {
    mocks: {
      $route,
      $router
    }
  }
} as MountingOptions<{}>

Work for me. No need any other changes.

@calebbergman

So actually fixing the types here seems complex - type-safety for vue files seems like it’s a fair way off. Works great with components using render functions, though.

Anyway, here is what I would currently recommend:

    const wrapper = mount(Layout, {
      data() {
        return {
          loggedIn: true
        }
      },
    } as any);

Notice as any at the end. Yes this kind of sucks but I don’t see a good way to otherwise solve this - I think some work is needed both in Vue core around the types and in VTU’s mount types, it’s pretty complicated as you can imagine.

I will update the docs - I hope this will let you continue testing your components 👍

The project was a fresh CLI project using vue@3.0.0-0. Upgrading to vue@3.0.0-rc.10 yields the same error, I’m afraid.

vue-shims.d.ts

declare module '*.vue' {
  import { defineComponent } from 'vue';

  const component: ReturnType<typeof defineComponent>;
  export default component;
}

Seems this is (mostly) solved. For specific reproductions, please open a new issue and we can fix them on a case by case basis.

Another way to solve the issue is to use InstanceType.

function getWrapper(): VueWrapper<InstanceType<typeof PatientInfo>> {
  return mount(PatientInfo);
}

@lmiller1990, Thanks a million for pointing to create a minimal reproducible project.

It turns out it is caused by a complex type in prop type. in Test file, the mount does not agree with the shape literal type. using as TargetType in the props value solves it. The complex type is from highcharts and it is ridiculous complex from nested A extends B syntax. Would be better to define our own type instead of using the provided ones.

So if anyone is still having an issue with this, you need to correctly match your types that you’re using for your component’s PropType with what you’re passing in on the test mount.

They need to explicitly match for the typescript engine to confirm parity.

maybe the vite startup page give you some inspiration: Provide types for *.vue imports tsconfig setup:
1. Install and add @vuedx/typescript-plugin-vue to tsconfig plugins
2. Delete shims-vue.d.ts
3. Open src/main.ts in VSCode
4. Open VSCode command input
5. Search and run “Select TypeScript version” -> “Use workspace version”

Both as any and as MountingOptions<{}> works for me. Thanks. But it will be great to see proper fix some day. Is it an issue of vue-test-utils?

Sure, ideally it would be great if we do not need to do as, but this is a good work around. Another alternative is as any.

That seems to have done the trick; this work around will be fine for now. Thank you for banging your head against this; I wish you success in getting all the types to behave in the future 😉

I will focus on the types a bit more soon. I am migrating a lib from VTU v1 + JS -> VTU v2 + TS and it’s an absolute nightmare. Note the error in mount(Foo)

Edit: it should be global.mocks but same problem happens.

image

I also get a typing error for this:

const wrapper = mount(Foo, {
  global: {
    config: {
      errorHandler: () => {}
    }
  }
})

I don’t really know how to fix these kind of TS errors yet.