CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-tauri-apps--api

TypeScript/JavaScript API bindings for Tauri applications providing comprehensive desktop app functionality

Pending
Overview
Eval results
Files

testing.mddocs/

Testing & Mocks

Mock implementations and testing utilities for developing and testing Tauri applications without requiring the full Tauri runtime environment.

Capabilities

IPC Mocking

Mock the core IPC (Inter-Process Communication) system to simulate backend responses during testing.

/**
 * Mock the IPC system with custom command handlers
 * @param cb - Function to handle mocked IPC commands
 * @param options - Optional configuration for mocking behavior  
 */
function mockIPC(
  cb: (cmd: string, payload?: InvokeArgs) => unknown, 
  options?: MockIPCOptions
): void;

interface MockIPCOptions {
  /** Whether to mock event system as well */
  shouldMockEvents?: boolean;
}

type InvokeArgs = Record<string, unknown> | number[] | ArrayBuffer | Uint8Array;

Window Mocking

Mock window instances and labels for testing window-related functionality.

/**
 * Mock window instances for testing
 * @param current - Label of the current window
 * @param additionalWindows - Labels of additional windows to mock
 */
function mockWindows(current: string, ...additionalWindows: string[]): void;

File Source Conversion Mocking

Mock file path to URL conversion for testing asset loading.

/**
 * Mock file source conversion for testing
 * @param osName - Operating system name to simulate ('linux', 'darwin', 'windows')
 */
function mockConvertFileSrc(osName: string): void;

Mock Cleanup

Clear all active mocks and restore normal Tauri behavior.

/**
 * Clear all mocks and restore normal Tauri functionality
 */
function clearMocks(): void;

Usage Examples

Basic IPC Mocking

import { mockIPC, invoke } from '@tauri-apps/api/core';
import { clearMocks } from '@tauri-apps/api/mocks';

// Mock IPC commands for testing
mockIPC((cmd, args) => {
  switch (cmd) {
    case 'get_user_data':
      return { name: 'Test User', email: 'test@example.com' };
    case 'save_file':
      console.log('Mock: Saving file', args);
      return { success: true, path: '/mock/path/file.txt' };
    case 'calculate':
      const { a, b } = args as { a: number; b: number };
      return { result: a + b };
    default:
      throw new Error(`Unknown command: ${cmd}`);
  }
});

// Test the application code
async function testUserData() {
  const userData = await invoke('get_user_data');
  console.log('User data:', userData); // { name: 'Test User', email: 'test@example.com' }
  
  const saveResult = await invoke('save_file', { content: 'Hello', filename: 'test.txt' });
  console.log('Save result:', saveResult); // { success: true, path: '/mock/path/file.txt' }
  
  const calcResult = await invoke('calculate', { a: 5, b: 3 });
  console.log('Calculation:', calcResult); // { result: 8 }
}

await testUserData();

// Clean up mocks
clearMocks();

Testing with Mock Events

import { mockIPC } from '@tauri-apps/api/mocks';
import { listen, emit } from '@tauri-apps/api/event';

// Mock IPC with events enabled
mockIPC((cmd, args) => {
  if (cmd === 'start_background_task') {
    // Simulate background task that emits progress events
    setTimeout(() => emit('task-progress', { progress: 0.5 }), 100);
    setTimeout(() => emit('task-complete', { result: 'done' }), 200);
    return { taskId: 'mock-task-123' };
  }
  return null;
}, { shouldMockEvents: true });

// Test event-driven functionality
async function testBackgroundTask() {
  // Set up event listeners
  const progressListener = await listen('task-progress', (event) => {
    console.log('Progress:', event.payload.progress);
  });
  
  const completeListener = await listen('task-complete', (event) => {
    console.log('Task complete:', event.payload.result);
    progressListener(); // Clean up listener
    completeListener();
  });
  
  // Start the task
  const result = await invoke('start_background_task', { data: 'test' });
  console.log('Task started:', result);
}

await testBackgroundTask();
clearMocks();

Window Mocking for Testing

import { mockWindows } from '@tauri-apps/api/mocks';
import { getCurrentWindow, Window } from '@tauri-apps/api/window';

// Mock multiple windows
mockWindows('main-window', 'settings-window', 'about-window');

async function testWindowFunctionality() {
  // Get current window (will be 'main-window')
  const currentWindow = getCurrentWindow();
  console.log('Current window label:', currentWindow.label); // 'main-window'
  
  // Get all windows
  const allWindows = await Window.getAll();
  console.log('All windows:', allWindows.map(w => w.label)); 
  // ['main-window', 'settings-window', 'about-window']
  
  // Get specific window
  const settingsWindow = await Window.getByLabel('settings-window');
  console.log('Settings window found:', settingsWindow !== null); // true
  
  // Test window operations (these will be no-ops in mock)
  await currentWindow.setTitle('Test Title');
  await currentWindow.setSize({ width: 800, height: 600 });
}

await testWindowFunctionality();
clearMocks();

File Source Mocking

import { mockConvertFileSrc } from '@tauri-apps/api/mocks';
import { convertFileSrc } from '@tauri-apps/api/core';

// Mock file conversion for different platforms
mockConvertFileSrc('linux');

function testFileConversion() {
  const linuxPath = convertFileSrc('/home/user/image.png');
  console.log('Linux path:', linuxPath); // Mock Linux-style conversion
  
  clearMocks();
  mockConvertFileSrc('windows');
  
  const windowsPath = convertFileSrc('C:\\Users\\User\\image.png');
  console.log('Windows path:', windowsPath); // Mock Windows-style conversion
  
  clearMocks();
  mockConvertFileSrc('darwin');
  
  const macPath = convertFileSrc('/Users/user/image.png');
  console.log('macOS path:', macPath); // Mock macOS-style conversion
}

testFileConversion();
clearMocks();

Jest Testing Integration

// test-utils.ts
import { mockIPC, mockWindows, clearMocks } from '@tauri-apps/api/mocks';

export function setupTauriMocks() {
  // Mock basic IPC commands
  mockIPC((cmd, args) => {
    switch (cmd) {
      case 'get_config':
        return { theme: 'dark', language: 'en' };
      case 'set_config':
        return { success: true };
      case 'get_version':
        return '1.0.0';
      default:
        return null;
    }
  });
  
  // Mock windows
  mockWindows('main');
}

export function teardownTauriMocks() {
  clearMocks();
}

// app.test.ts
import { setupTauriMocks, teardownTauriMocks } from './test-utils';
import { invoke } from '@tauri-apps/api/core';

describe('Application Tests', () => {
  beforeEach(() => {
    setupTauriMocks();
  });
  
  afterEach(() => {
    teardownTauriMocks();
  });
  
  test('should load configuration', async () => {
    const config = await invoke('get_config');
    expect(config).toEqual({ theme: 'dark', language: 'en' });
  });
  
  test('should save configuration', async () => {
    const result = await invoke('set_config', { theme: 'light' });
    expect(result).toEqual({ success: true });
  });
  
  test('should get version', async () => {
    const version = await invoke('get_version');
    expect(version).toBe('1.0.0');
  });
});

Advanced Mock Scenarios

import { mockIPC } from '@tauri-apps/api/mocks';

// Mock complex async operations
mockIPC(async (cmd, args) => {
  switch (cmd) {
    case 'fetch_data':
      // Simulate network delay
      await new Promise(resolve => setTimeout(resolve, 100));
      return { data: ['item1', 'item2', 'item3'] };
      
    case 'upload_file':
      // Simulate file upload with progress
      const { file } = args as { file: string };
      await new Promise(resolve => setTimeout(resolve, 50));
      return { 
        success: true, 
        url: `https://example.com/files/${file}`,
        size: 1024 
      };
      
    case 'database_query':
      // Simulate database operations
      const { query } = args as { query: string };
      if (query.includes('SELECT')) {
        return { rows: [{ id: 1, name: 'Test' }] };
      } else if (query.includes('INSERT')) {
        return { insertedId: 42 };
      }
      throw new Error('Invalid query');
      
    default:
      throw new Error(`Command not mocked: ${cmd}`);
  }
});

// Test error scenarios
async function testErrorHandling() {
  try {
    await invoke('invalid_command');
  } catch (error) {
    console.log('Expected error:', error.message); // "Command not mocked: invalid_command"
  }
  
  try {
    await invoke('database_query', { query: 'DROP TABLE users' });
  } catch (error) {
    console.log('Database error:', error.message); // "Invalid query"
  }
}

await testErrorHandling();

Mock State Management

import { mockIPC } from '@tauri-apps/api/mocks';

// Create stateful mocks
class MockDatabase {
  private data: Record<string, any> = {};
  
  get(key: string) {
    return this.data[key] || null;
  }
  
  set(key: string, value: any) {
    this.data[key] = value;
    return { success: true };
  }
  
  list() {
    return Object.keys(this.data);
  }
  
  clear() {
    this.data = {};
    return { success: true };
  }
}

const mockDb = new MockDatabase();

mockIPC((cmd, args) => {
  switch (cmd) {
    case 'db_get':
      const { key } = args as { key: string };
      return mockDb.get(key);
      
    case 'db_set':
      const { key: setKey, value } = args as { key: string; value: any };
      return mockDb.set(setKey, value);
      
    case 'db_list':
      return mockDb.list();
      
    case 'db_clear':
      return mockDb.clear();
      
    default:
      return null;
  }
});

// Test stateful operations
async function testStatefulMocks() {
  // Set some data
  await invoke('db_set', { key: 'user:1', value: { name: 'Alice' } });
  await invoke('db_set', { key: 'user:2', value: { name: 'Bob' } });
  
  // Retrieve data
  const user1 = await invoke('db_get', { key: 'user:1' });
  console.log('User 1:', user1); // { name: 'Alice' }
  
  // List keys
  const keys = await invoke('db_list');
  console.log('Keys:', keys); // ['user:1', 'user:2']
  
  // Clear all data
  await invoke('db_clear');
  const keysAfterClear = await invoke('db_list');
  console.log('Keys after clear:', keysAfterClear); // []
}

await testStatefulMocks();

Testing Best Practices

import { mockIPC, mockWindows, clearMocks } from '@tauri-apps/api/mocks';

// Utility for consistent mock setup
export class TauriTestEnvironment {
  private mockHandlers = new Map<string, Function>();
  
  mockCommand(cmd: string, handler: Function) {
    this.mockHandlers.set(cmd, handler);
  }
  
  setup() {
    mockIPC((cmd, args) => {
      const handler = this.mockHandlers.get(cmd);
      if (handler) {
        return handler(args);
      }
      throw new Error(`Unmocked command: ${cmd}`);
    });
    
    mockWindows('main-window');
  }
  
  teardown() {
    clearMocks();
    this.mockHandlers.clear();
  }
}

// Usage in tests
describe('Feature Tests', () => {
  let testEnv: TauriTestEnvironment;
  
  beforeEach(() => {
    testEnv = new TauriTestEnvironment();
    
    // Set up common mocks
    testEnv.mockCommand('get_config', () => ({ theme: 'light' }));
    testEnv.mockCommand('save_config', () => ({ success: true }));
    
    testEnv.setup();
  });
  
  afterEach(() => {
    testEnv.teardown();
  });
  
  test('feature specific test', async () => {
    // Add feature-specific mocks
    testEnv.mockCommand('feature_command', (args) => {
      return { result: `processed ${args.input}` };
    });
    
    // Test the feature
    const result = await invoke('feature_command', { input: 'test' });
    expect(result.result).toBe('processed test');
  });
});

Mock Limitations

The mocking system has some limitations to be aware of:

  • Window operations: Mocked windows don't actually create system windows
  • File system: File operations still need proper mocking at the command level
  • Platform APIs: Platform-specific features may not be fully mockable
  • Event timing: Event emission timing is simplified in mocks
  • Resource management: Mocked resources don't have the same lifecycle as real ones

For comprehensive testing, combine Tauri mocks with other testing tools and consider running integration tests with the actual Tauri runtime when possible.

Install with Tessl CLI

npx tessl i tessl/npm-tauri-apps--api

docs

app-lifecycle.md

core-ipc.md

events.md

index.md

menu-system.md

system-integration.md

testing.md

utilities.md

window-management.md

tile.json