Test Tauri v2 Updater Locally with a Mock Server
A Tauri updater test can fail quietly. The application starts, the update banner never appears, and there is no useful UI error. That does not always mean your updater code is wrong.
For a local test, you need four things to line up: signed updater artifacts, a valid updater JSON response, a mock server that the WebView can fetch, and a test-only setting that permits local HTTP.
This article shows a local test loop that gives you request-level evidence instead of guessing from the UI.
The symptom: update checks return no banner and no clear error
The common failure looks like this:
check()runs from the frontend or Rust side- the application stays on the current version
- no update banner appears
- the mock server receives no request, or receives only one request
- the production updater endpoint is not involved
This symptom has several possible causes. The updater artifact may not be signed. The JSON may contain the wrong signature value. The WebView may block the mock server because of CORS. Tauri may reject the endpoint because it is HTTP instead of HTTPS.
Do not start by rewriting the UI. First prove where the request stops.
Build a signed updater artifact before testing
Tauri updater installations require signatures. A normal installer is not enough for a realistic updater test.
Use a release build command that creates updater artifacts:
npm run tauri -- build --features updater --config src-tauri/tauri.updater.conf.json
Then check that the expected signature exists:
Get-ChildItem src-tauri\target\release\bundle\nsis -Filter *.sig
If there is no .sig file, stop. A mock server cannot fix a missing updater artifact.
Serve updater JSON and installer files from a local mock
Use a small server that logs each request. The log is the first piece of evidence.
const http = require("node:http");
const fs = require("node:fs");
const path = require("node:path");
const port = 8899;
const bundleDir = path.join("src-tauri", "target", "release", "bundle", "nsis");
const installerName = "my-app_1.0.1_x64-setup.exe";
const signatureName = `${installerName}.sig`;
const server = http.createServer((req, res) => {
console.log(`${req.method} ${req.url}`);
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
if (req.method === "OPTIONS") {
res.writeHead(204);
res.end();
return;
}
if (req.url === "/updater.json") {
const signature = fs
.readFileSync(path.join(bundleDir, signatureName), "utf8")
.trim();
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({
version: "1.0.1",
notes: "Local updater test",
pub_date: new Date().toISOString(),
platforms: {
"windows-x86_64": {
signature,
url: `http://127.0.0.1:${port}/${installerName}`
}
}
}));
return;
}
if (req.url === `/${installerName}`) {
const installerPath = path.join(bundleDir, installerName);
res.writeHead(200, {
"Content-Type": "application/octet-stream",
"Content-Length": fs.statSync(installerPath).size
});
fs.createReadStream(installerPath).pipe(res);
return;
}
res.writeHead(404);
res.end("not found");
});
server.listen(port, "127.0.0.1", () => {
console.log(`Updater mock server listening on http://127.0.0.1:${port}`);
});
The CORS headers are not decorative. The updater request is made from the WebView runtime, so a local server that works in curl may still fail in the app if the browser layer blocks it.
Add temporary HTTP-only test config
Tauri updater endpoints should use HTTPS in production. For a local mock server, use a test-only config overlay:
{
"plugins": {
"updater": {
"endpoints": ["http://127.0.0.1:8899/updater.json"],
"dangerousInsecureTransportProtocol": true
}
}
}
Keep this setting out of production release config. The name is accurate: it allows insecure transport. It is useful for a local test and inappropriate for real distribution.
If you need repeatable tests, name the file clearly:
src-tauri/tauri.updater.local-test.conf.json
Then build the local test variant with both the updater overlay and the local endpoint overlay if your build tool supports multiple config inputs, or with a dedicated combined test config.
Verify requests, download, install, and relaunch
Run the test in stages.
First, confirm the manifest request:
GET /updater.json
If the mock server does not log this request, the app did not reach your endpoint. Check endpoint config, feature gating, plugin initialization, and HTTP allowance.
Second, confirm the installer request:
GET /my-app_1.0.1_x64-setup.exe
If only the manifest request appears, check the JSON fields:
versionis greater than the installed app versionplatforms.windows-x86_64.urlpoints to the installer or updater bundle you serveplatforms.windows-x86_64.signaturecontains the.sigfile content- the mock server returns
200
Third, confirm install behavior. On Windows, the updater installation step can exit the application. If your UI disappears during install, that may be expected. Verify by checking whether the new installed version starts after relaunch.
Keep the mock test separate from release config
A good updater test setup has a hard boundary:
- production config uses HTTPS
- local mock config may use HTTP
- production config never enables
dangerousInsecureTransportProtocol - local mock server logs every request
- test artifacts are disposable
Do not keep the local endpoint in the same file used for release builds. That creates a release risk that is easy to miss during a rushed deploy.
Troubleshooting checklist
Use this order before changing application code:
- Does the build produce a
.sigfile? - Does the mock server start and serve
/updater.json? - Does the app request
/updater.json? - Does the app request the installer or updater bundle URL?
- Does the version in JSON exceed the installed app version?
- Does the signature field contain file content, not a path?
- Is HTTP enabled only in the local test config?
- Does the app relaunch or otherwise show the new version after install?
Each step narrows the failure. Without this separation, a missing signature can look like a UI bug, and a CORS failure can look like a bad updater API call.
References
- Tauri updater plugin
- Tauri process plugin
- Tauri v2 Updater Builds Need a Separate Config Overlay
- Testing Tauri Zustand Stores with Vitest: Mock IPC and Reset Singletons
Summary
A local updater test should prove the network path, the manifest, the signed artifact, and the install step. Use a mock server with CORS headers, enable HTTP only in a test config, and rely on request logs before trusting the UI.