[Vue Unit tesing] Testing API Calls (mocking)

Zhentiw發表於2024-11-30

In this lesson, we will be learning how to mock REST API calls. Mocking is a technique used in software testing to simulate the behavior of objects or systems that the software being tested depends on. This is done to isolate the software being tested from the variables outside of its control, such as network connections or external services.

Mocking is important in testing because it allows developers to test their code more thoroughly and efficiently. By creating mock objects or services, developers can test their code in isolation, without having to worry about the behavior of external dependencies.

📂src/components/PostCard.vue

<template>
  <div>
    <div v-if="post">
      <h1 data-testid="post-title">{{ post.title }}</h1>
      <p data-testid="post-body">{{ post.body }}</p>
    </div>
    <p v-if="loading" data-testid="loader">Loading...</p>
    <p v-if="error" data-testid="error-message">{{ error }}</p>
  </div>
</template>

<script setup>
import { ref, onMounted } from "vue";
import axios from "axios";

const post = ref(null);
const loading = ref(true);
const error = ref(null);

const fetchPost = async () => {
  try {
    const { data } = await axios.get(
      "https://jsonplaceholder.typicode.com/posts/1"
    );
    post.value = data;
  } catch (err) {
    error.value = err.message;
  } finally {
    loading.value = false;
  }
};

onMounted(() => {
  fetchPost();
});

</script>

What we’ll be testing

Before we get started, let’s decide on what we’ll be testing.

  1. First, we want to make sure that our app successfully makes an API request and displays the correct data if the request is successful.
  2. Next, we want to test that our app properly handles errors by displaying an error message if the API request fails.

Before we mount our app, we need to be able to intercept the GET request made using Axios and return a mocked value. We do this because we don’t want to make an actual request to the real API server. Remember, mocks allow us to test in isolation.

To do this, we can use the spyOn function from Vitest. Vitest provides utility functions to help you with this through its vi helper. You can import { vi } from 'vitest' or access it globally when the Vitest global configuration is enabled.

Let’s configure it globally. This way, we can use functions (such as describe, expect and test) from Vitest without having to import them every single time.

In your Vitest config file set globals to true:

📃 vitest.config.js

import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    globals: true,
  },
})

📁src/components/tests/PostCard.test.js

import axios from 'axios'
import PostCard from '../PostCard.vue'
import { mount, flushPromises } from '@vue/test-utils'

const mockPost = {
  userId: 1,
  id: 1,
  title: 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit',
  body: 'quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto'
}

describe('Post Card Component', () => {
  test('can fetch and display a post', async () => {
    vi.spyOn(axios, 'get').mockResolvedValueOnce({ data: mockPost })

    const wrapper = mount(PostCard)

    expect(wrapper.html()).toContain('Loading...')

    await flushPromises()

    // new
    expect(wrapper.find('[data-testid="post-title"]').text()).toBe(mockPost.title)

    expect(wrapper.find('[data-testid="post-body"]').text()).toBe(mockPost.body)
  })

  test('can display an error message if fetching a post fails', async () => {
    vi.spyOn(axios, 'get').mockRejectedValueOnce(new Error('Error occurred'))

    const wrapper = mount(PostCard)

    expect(wrapper.html()).toContain('Loading...')

    await flushPromises()

    expect(wrapper.find('[data-testid="error-message"]').text()).toBe('Error occurred')
  })
})

Since the process of fetching our post is asynchronous, we need to ensure that the promise operation has been completed before writing our assertion.

To do this, we make use of flushPromises, a utility method from Vue test utils that flushes all pending resolved promise handlers. You can await the call of flushPromises to flush pending promises and improve the readability of your test.

相關文章