5

I am trying to write a test, using vitest, to assert a computed property in a vue3 component that is defined with script setup.

Consider a simple component:

// simple.vue
<script lang="ts" setup>
import { computed } from 'vue';

const hello = computed((): string => {
  return 'Hello';
});
</script>

<template>
  {{ hello }}
</template>

My test is like this:

describe('Hello', () => {
  it('should compute hello', () => {
    const wrapper = mount(Hello);
    expect(wrapper.vm.hello).toBe('Hello');
  });
});

This test actually works as expected when run using vitest, so functionally things seem to be working well.

However, VSCode cannot see the computed properties on the vm object:

enter image description here

It is able to see normal properties (e.g., those defined with the defineProps macro). Is this just a problem with VSCode-specific tooling, or is there another way I should be going about testing computed properties in vue3 components?

If this is the preferred method, is there a way to pull in the types of the computed properties (similar to how the types of the defined props seem to be pulled in)?

I have tried the technique described in this Vue Testing Handbook, but this doesn't work at all and I assume it must be specific to vue2.

1
  • It's totally possible because IDE, test rig and main setup has a different toolchain and don't behave similarly. It's a problem that comp API prefers FP and won't expose anything you need to test to the instance. Graybox testing with Vue Testing Library is preferable to not be limited with this Commented Mar 11, 2022 at 17:33

3 Answers 3

9

From Vue docs:

Components using <script setup> are closed by default - i.e. the public instance of the component, which is retrieved via template refs or $parent chains, will not expose any of the bindings declared inside <script setup>.

This also affects the type of the wrapper.vm in Vue Test Utils, such that it only includes public or exposed props of the <script setup> component.

In your case, use the defineExpose() compiler macro to expose hello:

<script lang="ts" setup>
import { computed } from 'vue';

const hello = computed((): string => {
  return 'Hello';
});
     👇
defineExpose({ hello });
</script>

screenshot

Sign up to request clarification or add additional context in comments.

Comments

3

I assume that the mount you are using is from @vue/test-utils. You can type the wrapper like this to have typescript autocompletion and don't have errors:

import {mount, VueWrapper} from "@vue/test-utils";
import HelloWorld from "@/components/HelloWorld.vue"
import { ComponentPublicInstance } from "vue";

type MyComponentProps = any
type MyComponentVariables = {
  hello: string
}

type MyComponentWrapperType = VueWrapper<ComponentPublicInstance<MyComponentProps, MyComponentVariables>>

describe('Hello', () => {
  it('should compute hello', () => {
    const wrapper: MyComponentWrapperType = mount(HelloWorld);
    expect(wrapper.vm.hello).toBe('Hello');
  });
});


The first generic type (here I put any) are the props type of your component and the second generic ({ bipbip: string }) are the types of your returned properties (what you return in a setup function). With <script setup> you can put directly all your variables.

2 Comments

This is helpful to point in the direction of how to properly, but manually, set the type of the returned wrapper. I have yet to get it to work with a non-trivial component but I'll play around with it. I'm still holding out hope for a way to get the computed properties inferred automatically, just like the "normal" props are.
Yes I agree with you. I hope @vue/test-utils will upgrade this
0

While the current accepted answer is accurate in that you can expose the property to test it, another part of the Vue docs recommends against exposing and testing the inner workings (ie: private state variables) of your component because testing implementation is inherently fragile and you should be instead testing behavior, ie: the use of the hello variable. This is the same as any other unit test, you test what comes out publicly, rather than testing specifics inside a given function:

describe('Hello', () => {
  it('should compute hello', () => {
    const wrapper = mount(Hello);
    expect(wrapper.html()).toContain('Hello');
  });
});

Another way to change this would be to add an identifier to the template of the component (ie: change {{hello}} to <span id="hello-span">{{hello}}</span>) and then search for that element in your test and look for specific content within it:

describe('Hello', () => {
  it('should compute hello', () => {
    const wrapper = mount(Hello);
    const helloSpan = wrapper.find('#hello-span');
    expect(helloSpan.html()).toContain('Hello');
  });
});

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.