Starting a project for me usually starts with setting up the testing infrastructure. The only exception is when one already exists. In this article we will learn two things. We will start from setting up vitest in the Tauri project. We will then learn how to write a test in JavaScript for a part that’s connecting to the Tauri Rust backend.
I already wrote about our Tauri experience in how to write unit tests in rust for Tauri. This time, we’re going to setup the tests for the frontend using Vitest. We’re also going to write a first meaningful test for a Tauri frontend and see how to work with the Rust part during JavaScript tests.
Table of Contents
How to Setup a Tauri App?
That’s relatively easy. Here are the steps:
- Make sure you have the prerequisite on your machine.
- cd into your projects folder
- Create a Tauri app using:
npm create tauri-app@latest
You will be asked a few questions during the app’s setup process.
For this tutorial, I’m going to select the following:
Need to install the following packages – ‘y’
✔ Project name · tauri-demo
✔ Choose which language to use for your frontend · TypeScript / JavaScript
✔ Choose your package manager · npm
✔ Choose your UI template · Vanilla
✔ Choose your UI flavor · TypeScript (yes, Tauri calls its frontend “UI” at times)
cd into the folder (in my case, tauri-demo
) and run npm install && npm run tauri dev
This will install the JavaScript dependencies and start the desktop app in dev mode.
If all worked correctly, you should see the app running like this:
That’s great! We now have a working greeter app!
How to Add Vitest to a Project?
The first step would be to install vitest
. That’s easy: npm i -D vitest
.
Once we have that, we can add the following script to our package.json
:
"test:frontend": "vitest"
so our package.json
file now looks like this:
Running our test code won’t do anything because the project has no tests. Let’s write the first one.
Writing Our First Test
We currently have one file in our project – main.ts
.
Let’s create a simple test for it. We will create a test file: main.spec.ts
in the same folder and fill it with this content:
In order to run the tests, we will use our npm script
:
npm run test:frontend
.
Try it out…
If you did everything right so far, the test should fail like this:
How to Configure Vitest for Frontend Testing?
The reason the test fails is that we are trying to use window
. Let’s fix that.
How to solve window is undefined
in Vitest?
vitest
‘s default environment is Nodejs
, and there’s no window
there. In order for it to work, we’ll need to setup jsdom
, which is a browser emulator for nodejs
. Here’s how you setup jsdom
in vitest
:
- Install
jsdom
:npm i -D jsdom
- Set
jsdom
as the environment invite.config.ts
:
Great! Let’s run the tests again: npm run test:frontend
.
Did the tests fail again? Great! Because they should. Let’s talk about the next error.
How to fix describe is not defined
in Vitest?
Here’s the error:
So now we need a way to define describe
and probably all of the other test functions. We could import them one by one from vitest
(or any other test framework for that matter). Another easy option would be to tell vitest
to expose the test functions as globals.
All we need to do is add globals: true
to the test configuration. The final file should look like this:
If we run npm run test:frontend
now we will get the beloved green screen:
Now that we have a working testing infrastructure, we can move on to the next step: Start building our app! Let’s write a meaningful test just to see it is working.
Writing the First Meaningful Test
Understanding the Tauri Code
Our test doesn’t say much. It just checks if true is truth. Because our main
module is already written, let’s see what it does. The first thing that’s going to run is this piece of code:
- On
DOMContentLoaded
- Get the input and message elements
- Set a submit
eventListener
on the form element, prevent its default and fire the greet function.
Let’s test the greet functionality, because it’s the main business logic:
This function checks if we have message and input elements. If they exist, it fills the message element with the result of the invoke
function.
The invoke
function is part of the Tauri API. It is the communication protocol between the JavaScript client
and the Rust backend. It is called Inter-Process Communication
, or IPC in short. In essence, we invoke
the greet
API with the payload:
{
name: greetInputEl.value,
}
In the folder src-tauri/src
there’s the main.rs
file, where the greet
API is implemented:
It is easy to see the string that’s supposed to return is
Hello, ${name}! You've been greeted from Rust!
Let’s summarize what our app is all about:
- Listen to DOMContentLoaded event
- Set an event listener on the greet form
- Set the greet message to
Hello, ${name}! You've been greeted from Rust!
That’s what our test needs to check. Let’s write it down.
Writing the Test
The first thing we need to do is change the it
description:
As part of the preparation phase, we need to setup the DOM elements:
Now we need to start the first piece of logic inside the DOMContentLoaded listener by dispatching the event:
window.dispatchEvent(new Event("DOMContentLoaded"));
Now let’s get a hold on our elements and set the input element with a value:
Now we come to a Tauri specific test part. We need to mock the Rust backend.
How to Mock the Tauri Backend
In order to mock the Tauri backend, we need to use Tauri test utilities. In this case, we want to mock the IPC. We will import mockIPC like this: import { mockIPC } from "@tauri-apps/api/mocks";
We then use it like this:
This is a very simple API, so the mock
looks much like the “real thing”.
What it’s going to do is intercept every call to invoke
and return the value returned from the callback.
Now we need two more things: dispatch the form’s submit event and wait for an event loop cycle for the API to return its value:
The first line is pretty straight forward. The second line is an old trick to move one “tick” ahead and await for async processes to settle down. Many people do not agree with this method, but sometimes they are a must and in vivid
we use them a lot in tests because of the async nature of the Fast templating system.
Finaly, we get to the assertion:
We expect the greet message element’s text to be the expected value.
If we run the test now it will pass. Here’s the full code of the test:
We can always refactor for clarity:
It’s still long, but note this code was not written with testability in mind. It’s legacy code we covered with a test.
The next step would be to create a login screen for the application, but this will wait for the next article.
Summary
Testing is a basic part of every development ecosystem. Vitest is the newest test runner in the JavaScript ecosystem. With this article, combined with the article about setting up unit tests in rust, you now know how to setup a fullstack testing infrastructure in a Tauri project.
In the next posts in this series, we are going to use the knowledge garnered in the articles to build a full application with Tauri as a desktop wrapper, Vivid for frontend components and Firebase for user management and database.
Thanks a lot to Hod Bauer for the kind and thorough review.