Vuetify 3 TypeScript Tutorial Series -Part 10

Habibi Coding | حبيبي كودنق
Nerd For Tech
Published in
7 min readFeb 22, 2024
tutorial banner

In Part 9 of this tutorial series, we covered the following steps:

  • Implemented logic to delete task
  • Start to write Unit Tests

If you missed Part 9, you can find it here: Part 9

Testing the task API

Before we write another Unit Test, we need to adjust our vite.config.mts after server:{} add these lines:

test: {
globals: true,
environment: 'jsdom',
server: {
deps: {
inline: ['vuetify'],
},
}
  1. Test Configuration:
  • globals: true - This enables the usage of global variables in the test environment, meaning you don't need to import Vue-related functions (like describe, it) in every test file.
  • environment: 'jsdom' - Sets the test environment to 'jsdom', a JavaScript implementation of various web standards, primarily used for testing web pages and JavaScript libraries in Node.js.
  • server: { deps: { inline: ['vuetify'] }, } - This configuration is specific to handling dependencies within the test server. It inlines dependencies related to Vuetify in the test bundle. This can be important for avoiding issues related to how Vuetify (or similar libraries) might be processed or transformed during tests.

Overall, this configuration file is setting up the Vite environment for testing a Vue.js application that uses Vuetify. It ensures that global functions are available in tests, uses jsdom as the test environment, and correctly handles dependencies for Vuetify.

In the previous part, we tested the formatDate.ts , now we need to test the taskApi.ts , so create the in tests -> impl the file taskApi.test.ts and add these lines:

import {describe, expect, it} from 'vitest';
import {taskService} from '../../src/services/taskApi';
import {rest} from "msw";
import {server} from "../../src/setupTests";
import {HTTP_STATUS} from "../../src/constants/appConstants";
import {Priority, TaskCreateRequest, TaskUpdateRequest} from "../../src/dtos/taskDto";

describe('taskService Unit Tests', () => {
it('when fetch tasks is triggered then expect two items', async () => {
const response = await taskService.getTasks('open');
expect(response.status).toBe(HTTP_STATUS.OK);
expect(response.data.length).toBe(2);
});

it('when creating a task then expect response to be equal request', async () => {
const request: TaskCreateRequest = {
description: 'workout',
isReminderSet: true,
isTaskOpen: true,
priority: Priority[Priority.MEDIUM]
}
const response = await taskService.createTask(request);
expect(response.status).toBe(HTTP_STATUS.CREATED);
expect(response.data).toEqual(request);
});

it('when delete a task request ist send then expect 204 back', async () => {
const response = await taskService.deleteTask(9);
expect(response.status).toBe(HTTP_STATUS.NO_CONTENT);
});

it('when updating a task then expect response to be equal request', async () => {
const request: TaskUpdateRequest = {
description: 'buy groceries',
isTaskOpen: true,
isReminderSet: false,
priority: Priority[Priority.HIGH],
};
const response = await taskService.updateTask(4, request);
expect(response.status).toBe(HTTP_STATUS.OK);
expect(response.data).toEqual(request);
});

it('when bad task creation request is send then expect 400 back ', async () => {
server.use(
rest.post("https://backend4frontend.onrender.com/api/v1/tasks", (req, res, ctx) => {
return res(ctx.status(400), ctx.json({message: "Bad Request"}));
})
);

const request: TaskCreateRequest = {
description: 'workout',
isReminderSet: true,
isTaskOpen: true,
priority: Priority.MEDIUM
};

let errorResponse;
try {
await taskService.createTask(request);
} catch (error) {
errorResponse = error;
}

expect(errorResponse).toBeDefined();
expect(errorResponse.response.status).toBe(HTTP_STATUS.BAD_REQUEST);
expect(errorResponse.response.data.message).toBe("Bad Request");
});

it('when wrong task creation request is send then expect 500 back', async () => {
server.use(
rest.post("https://backend4frontend.onrender.com/api/v1/tasks", (req, res, ctx) => {
return res(ctx.status(500), ctx.json({message: "Internal Server Error"}));
})
);

const request: TaskCreateRequest = {
description: 'workout',
isReminderSet: true,
isTaskOpen: true,
priority: Priority.MEDIUM
};

let errorResponse;
try {
await taskService.createTask(request);
} catch (error) {
errorResponse = error;
}

expect(errorResponse).toBeDefined();
expect(errorResponse.response.status).toBe(HTTP_STATUS.INTERNAL_SERVER_ERROR);
expect(errorResponse.response.data.message).toBe("Internal Server Error");
});
});

The tests use Vitest for testing and Mock Service Worker (MSW) for handling network requests.

  1. Fetch Tasks Test: Tests if fetching tasks with a status of ‘open’ returns two items and checks the HTTP status to be 200 (OK).
  2. Create Task Test: Validates that creating a task returns a response equal to the request data. It checks the HTTP status to be 201 (Created) and the response data to match the request.
  3. Delete Task Test: Tests the deletion of a task by ID and expects a 204 (No Content) HTTP status, indicating successful deletion.
  4. Update Task Test: Checks if updating a task returns a response that matches the request data. It expects a 200 (OK) HTTP status and the response data to be equal to the request.
  5. Bad Task Creation Test (400 Error): Simulates a bad request for task creation. It uses MSW to return a 400 (Bad Request) status when the create task endpoint is called. The test checks if the error response is defined, has the correct status, and contains the expected error message.
  6. Wrong Task Creation Test (500 Error): Simulates an internal server error for task creation. It sets up MSW to return a 500 (Internal Server Error) status. The test validates that the error response is as expected, with the correct status and error message.

These tests ensure that the taskService correctly handles various scenarios, including successful operations and error handling for different HTTP status codes.

Now, just run in the Terminal:

npx vitest run
npx vitest run

You should see this result:

8 Tests passed

Test edit task

Next, we should write a test for the composable file editTask.ts so create in tests -> impl the file editTask.test.ts and add these lines:

import {describe, expect, it, vi} from 'vitest';
import {taskService} from "../../src/services/taskApi";
import {editTask} from "../../src/composables/editTask";
import {AxiosError} from "axios";
import {ref, Ref} from "vue";
import {mockTaskUpdateRequest} from "../helper/mockResponse";


describe('editTask tests', () => {
const id = 1;
const request = {title: 'New Task', completed: false};
const isLoading: Ref<boolean> = ref(false);
const isNetworkError: Ref<boolean> = ref(false);
const axiosError: Ref<AxiosError | unknown> = ref(null);
const navigateToTasksView = vi.fn();

it('when update task is called then expect success path', async () => {
taskService.updateTask = async () => ({data: mockTaskUpdateRequest});


await editTask(id, request, isLoading, isNetworkError, axiosError, navigateToTasksView);

expect(isLoading.value).toBe(false);
expect(isNetworkError.value).toBe(false);
expect(axiosError.value).toBe(null);
expect(navigateToTasksView).toHaveBeenCalled();
});

it('when update task is called then expect network error', async () => {
const errorMessage = 'Network error';
const mockError = new AxiosError(errorMessage);
taskService.updateTask = vi.fn(() => Promise.reject(mockError));

await editTask(id, request, isLoading, isNetworkError, axiosError, navigateToTasksView);

expect(isLoading.value).toBe(false);
expect(isNetworkError.value).toBe(true);
expect(axiosError.value).toEqual(mockError);
expect(mockError.message).toEqual(errorMessage);
});
});

Success Path Test:

  • The first test (“when update task is called then expect success path”) mocks a successful update scenario.
  • It sets taskService.updateTask to return a successful response.
  • After calling editTask, it checks that isLoading is false, isNetworkError is false, and axiosError is null, indicating a successful update.
  • It also verifies that navigateToTasksView was called, indicating the user would be navigated to another view after the update.

Network Error Test:

  • The second test (“when update task is called then expect network error”) simulates a network error during task update.
  • It sets taskService.updateTask to reject with a mock AxiosError.
  • After calling editTask, it checks that isLoading is false (loading finished), isNetworkError is true (indicating an error occurred), and axiosError is equal to the mock error.
  • It also verifies the message of the mock error is as expected.

Overall, these tests aim to ensure the editTask function behaves correctly both in successful scenarios and when an error occurs, such as a network issue.

Test generating task

After that, we will write similar tests for the composable file generateTask.ts so create in tests -> impl the file generateTask.test.ts and add these lines:

import {describe, expect, it, vi} from 'vitest';
import {taskService} from "../../src/services/taskApi";
import {generateTask} from "../../src/composables/generateTask";
import {Ref, ref} from "vue";
import {AxiosError} from "axios";
import {mockTaskCreateRequest} from "../helper/mockResponse";

describe('generateTask tests', () => {
const request = {title: 'New Task', completed: false};
const isLoading: Ref<boolean> = ref(false);
const isNetworkError: Ref<boolean> = ref(false);
const axiosError: Ref<AxiosError | unknown> = ref(null);
const navigateToTasksView = vi.fn();

it('when create task is called then expect success path', async () => {
taskService.createTask = async () => ({data: mockTaskCreateRequest});

await generateTask(request, isLoading, isNetworkError, axiosError, navigateToTasksView);

expect(isLoading.value).toBe(false);
expect(isNetworkError.value).toBe(false);
expect(axiosError.value).toBe(null);
expect(navigateToTasksView).toHaveBeenCalled();
});

it('when create task is called then expect network error', async () => {
const errorMessage = 'Network error';
const mockError = new AxiosError(errorMessage);
taskService.createTask = vi.fn(() => Promise.reject(mockError));

await generateTask(request, isLoading, isNetworkError, axiosError, navigateToTasksView);

expect(isLoading.value).toBe(false);
expect(isNetworkError.value).toBe(true);
expect(axiosError.value).toEqual(mockError);
expect(mockError.message).toEqual(errorMessage);
});
});

Success Path Test:

  • The first test (“when create task is called then expect success path”) mocks a successful task creation.
  • taskService.createTask is mocked to return a resolved promise with mockTaskCreateRequest data.
  • The generateTask function is called with the necessary arguments.
  • Post-execution, it checks if isLoading is false (indicating loading has ended), isNetworkError is false (no network error occurred), and axiosError is null (no error was caught).
  • It also verifies if navigateToTasksView was called, simulating a successful navigation after task creation.

Network Error Test:

  • The second test (“when create task is called then expect network error”) simulates a network error during task creation.
  • taskService.createTask is mocked to return a rejected promise with a mockError (an AxiosError instance).
  • After calling generateTask, it asserts that isLoading is false, isNetworkError is true (error occurred), and axiosError matches mockError.
  • It also checks if the error message of mockError matches the expected errorMessage.

These tests ensure that the generateTask function correctly handles both successful task creation and errors (like network issues), updating the application's state accordingly.

With that, we conclude the first part of this tutorial series. If you found it useful and informative, give it a clap. Here is Part 11

Don’t forget to check out the video playlist on YouTube.

Here is the source code on GitHub, check out the branch: part-ten

--

--