Search code examples
vue.jsunit-testingvue-composition-apivitest

How to unit test VUE 3 Composition API methods?


How to unit test methods in VUE 3 Composition API (using vitest (preferably) or jest), knowing that methods are not accesible/exportable directly from the <script setup lang="ts"></script> block?

Here is the HelloWorld.vue:

<script setup lang="ts">
defineProps<{
  msg: string
}>()

const sum = (a: number, b: number) => {
  return a + b;
}
</script>

<template>
  <div class="greetings">
    <h1 class="green">{{ msg }}</h1>
    <h3>
      You’ve successfully created a project with
      <a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> +
      <a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next?
    </h3>
  </div>
</template>

Here is the HelloWorld.spec.ts test file

import { describe, it, expect } from "vitest";

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

describe("HelloWorld", () => {
    it("renders properly", () => {
        const wrapper = mount(HelloWorld, { props: { msg: "Hello Vitest" } });
        expect(wrapper.text()).toContain("Hello Vitest");
    });
});

that tests component integration, passing and using props. But how to test the sum method?

I've tried:

test('adds 1 + 2 to equal 3', () => {
    const wrapper = mount(HelloWorld, { props: { msg: "Hello Vitest" } });

    // Access the sum function from the component instance
    const instance = wrapper.vm;
    const result = instance.sum(1, 2);

    expect(result).toBe(3);
})

I've also tried const result = wrapper.vm.$options.setup().sum(1, 2);

Also these kind of approaches:

const vm = mount(HelloWorld, { props: { msg: "Hello Vitest" } }).vm;
const sumFunction = vm.$options.setup().sum;
...

etc.


Solution

  • I've made it work :)

    IMPORTANT is that this solution does NOT require any change in the component that is being tested - no need for "exposing" methods

    Here is HelloWorld.spec.ts:

    import { describe, it, test, expect } from "vitest";
    
    import { mount } from "@vue/test-utils";
    import HelloWorld from "../HelloWorld.vue";
    
    describe("HelloWorld", () => {
        const wrapper = mount(HelloWorld, { props: { msg: "Hello Vitest" } });
        it("renders properly", () => {
            expect(wrapper.text()).toContain("Hello Vitest");
        });
        test('sum: adds 1 + 2 to equal 3', () => {
            // Access the Component method from the Composition API
            const instance = wrapper.vm;
    
            // The sum function is available within the instance
            const sumFunction = (instance as any).sum;
    
            const result = sumFunction(1, 2);
    
            expect(result).toBe(3);
          });
          test('mul: muls 2 * 3 to equal 6', () => {
            // Access the Component method from the Composition API
            const instance = wrapper.vm;
    
            // The mul function is available within the instance
            const mulFunction = (instance as any).mul;
    
            const result = mulFunction(2, 3);
    
            expect(result).toBe(6);
          })
    });
    
    

    and here is the component HelloWorld.vue that is tested:

    <script setup lang="ts">
    defineProps<{
      msg: string
    }>()
    
    const sum = (a: number, b: number) => {
      return a + b;
    }
    
    const mul = (a: number, b: number) => {
      return a * b;
    }
    </script>
    
    <template>
      <div class="greetings">
        <h1 class="green">{{ msg }}</h1>
        <h3>
          You’ve successfully created a project with
          <a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> +
          <a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next?
        </h3>
      </div>
    </template>
    
    <style scoped>
    h1 {
      font-weight: 500;
      font-size: 2.6rem;
      position: relative;
      top: -10px;
    }
    
    h3 {
      font-size: 1.2rem;
    }
    
    .greetings h1,
    .greetings h3 {
      text-align: center;
    }
    
    @media (min-width: 1024px) {
      .greetings h1,
      .greetings h3 {
        text-align: left;
      }
    }
    </style>