This is a devlog! I work on a bunch of random software projects on my own time and the devlogs are a way to keep track of and talk about what I’ve done, because the one thing the world needs more of is MORE CONTENT.

This is my first devlog ever and the first for an app I’m currently working on to help me, well, live better, that I am creatively calling, mealplanner. It, uh, helps you plan meals.

I’ll (probably) write more about what it does and why I need it in another post, right now I’m just cataloging what I did in my last coding session: setup full coverage unit testing.

Some notes on the tech stack I’m using: I’m developing mealplanner currently as a desktop app that I will eventually want available as a mobile app too. It’s client only, I don’t want to have to host a server, and I want to be able to write it as quickly as I can. So I decided to use Vue running within Tauri, a lightweight version of Electron.

Getting started w/ Tauri wasn’t difficult, but I quickly noticed a shortcoming that doesn’t seem to be addressed in the docs: how to apply automated testing. I’m the only one working on this and I have limited time, so I definitely don’t want to have to test everything by hand to see if I’ve broken something when I make a change.

First I tried using a modern framework like Jest. This works for testing the Vue code by itself, but some of that code is dependent on Tauri. mealplanner, for example, uses a sqlite database, and the code that accesses and modifies it is provided via a Tauri plugin.

Jest runs in node, which has no simple way of running Tauri’s rust code, so if I were to use it, I’d have to provide mocks for every Tauri service that’s used, and the SQL queries the app uses would remain untested. Again, since I’m the only one working on this, that’s definitely not something I want.

So I figured if running tests in node isn’t possible, maybe I can run them within a Tauri browser, then it would have access to the Tauri platform and I’d be able to test everything I want to.

Jest doesn’t work in the browser (afaik), so I tried using mocha next. The first problem: how to run mocha in a Tauri browser without bundling my test code with my main app and without creating a whole new app just for tests.

I managed to solve this by creating a new entrypoint for tests that loaded mocha, referencing this as the main TypeScript file in a new ts-config.spec.json (oh, I’m using TypeScript for this app), and conditionally using this file in the vue.config.js file, if the NODE_ENV environment variable is set to test. Then I added a new script entry to package.json that runs NODE_ENV=test vue-cli-service tauri:serve.

This got me further, mocha would run in a Tauri browser, but none of the spec files would load. Turns out this is because, if mocha is loaded in a browser, it will try to load the files by adding new <script> elements. But Tauri apps use a bundle and don’t really have access to the original TypeScript source files (and even if they did, they wouldn’t be able to compile them in the browser).

This seems to be the case for every test runner that can run tests in the browser, so I couldn’t really switch to another framework. To get around this, I had to import every spec file manually in the tests entrypoint I created.

This led to another problem: the imports had to be the first part of the file, but mocha wouldn’t be setup until later on. (Doing requires afterward might work too, but my eslint config complained about using require. It might be easier in the end though, so I might change it to dynamically require spec files. If I can figure that out.)

I fixed this last problem by wrapping each spec file in a function that defined the actual test suite. For example:

export default (): void => {
    describe('MyDataAccessObject', () => {
        // empty
    });
};

These functions were called after mocha was setup.

And after this FINAL CHANGE… it still didn’t work. The describe() calls failed because they are meant to be called by mocha itself. A specific variable, currentContext, is initialized during the start of the run() function, and this is used by describe() and other functions to figure out what mocha context to attach a suite to.

Fortunately for me (very, very fortunately), I was able to find a hacky way to initialize this value manually:

// HACK: force currentContext to be initialized
mocha.suite.emit('pre-require', window, '...', mocha);

Which meant all the test code would load and run in a Tauri browser with access to Tauri plugins, and I did not just waste an entire day on a wild goose chase.