Skip to main content

Testing with Utah Scripts

Learn how to do testing with Utah scripts effectively with unit tests, integration tests, and best practices.

Test Structure

Basic Test Layout

// test/math.test.shx
import "../src/math.shx";

// Test addition function
function testAddition(): void {
let result: number = add(2, 3);
assert(result == 5, "Addition should return 5");
}

// Test subtraction function
function testSubtraction(): void {
let result: number = subtract(5, 3);
assert(result == 2, "Subtraction should return 2");
}

// Run tests
testAddition();
testSubtraction();
console.log("All tests passed!");

Test Organization

// test/utils.test.shx
import "../src/utils.shx";

// Test utilities
function assert(condition: boolean, message: string): void {
if (!condition) {
console.log("FAIL: ${message}");
script.exit(1);
} else {
console.log("PASS: ${message}");
}
}

function setupTest(): void {
console.log("Setting up test environment");

// Create test directory
"$(mkdir -p /tmp/test-data)";
}

function teardownTest(): void {
// Cleanup test environment
fs.delete("/tmp/test-data");
}

Unit Testing

Testing Functions

// src/calculator.shx
function multiply(a: number, b: number): number {
return a * b;
}

function divide(a: number, b: number): number {
if (b == 0) {
throw new Error("Division by zero");
}
return a / b;
}

// test/calculator.test.shx
import "../src/calculator.shx";

function testMultiply(): void {
assert(multiply(3, 4) == 12, "3 * 4 should equal 12");
assert(multiply(0, 5) == 0, "0 * 5 should equal 0");
assert(multiply(-2, 3) == -6, "-2 * 3 should equal -6");
}

function testDivide(): void {
assert(divide(10, 2) == 5, "10 / 2 should equal 5");
assert(divide(7, 3) == 2, "7 / 3 should equal 2 (integer division)");

// Test error handling
try {
divide(5, 0);
assert(false, "Division by zero should throw error");
} catch (error) {
assert(true, "Division by zero correctly throws error");
}
}

Testing File Operations

// test/file-operations.test.shx
function testFileOperations(): void {
let testFile: string = "/tmp/test.txt";

// Create file
fs.writeFile(testFile, "Hello, Utah!");
assert(fs.exists(testFile), "File should be created");

// Read file
let content: string = fs.readFile(testFile);
assert(content == "Hello, Utah!", "File content should match");

// Delete file
fs.delete(testFile);
assert(!fs.exists(testFile), "File should be deleted");
}

Integration Testing

Testing API Interactions

// test/api-integration.test.shx
function testApiIntegration(): void {
// Test API endpoint
let response: string = web.get("https://api.github.com/repos/polatengin/utah");
let data: object = json.parse(response);

assert(data.name == "utah", "Repository name should be 'utah'");
assert(data.full_name == "polatengin/utah", "Full name should match");
}

function testWebhookHandler(): void {
// Test webhook processing
let payload: string = "{\"action\": \"push\", \"repository\": {\"name\": \"utah\"}}";
let result: boolean = processWebhook(payload);

assert(result == true, "Webhook should be processed successfully");
}

Testing System Integration

// test/system-integration.test.shx
function testSystemIntegration(): void {
// Test system commands
let result: string = system.execute("echo 'test'");
assert(result.trim() == "test", "System command should return 'test'");

// Test environment variables
system.setEnv("TEST_VAR", "test_value");
let value: string = system.env("TEST_VAR");
assert(value == "test_value", "Environment variable should be set");
}

Test Automation

Test Runner Script

// test/run-tests.shx
let testFiles: string[] = [
"test/math.test.shx",
"test/calculator.test.shx",
"test/file-operations.test.shx",
"test/api-integration.test.shx"
];

let passed: number = 0;
let failed: number = 0;

for (let testFile of testFiles) {
console.log("Running ${testFile}...");

try {
system.execute("utah ${testFile}");
console.log("✅ ${testFile} passed");
passed++;
} catch (error) {
console.log("❌ ${testFile} failed");
failed++;
}
}

console.log("\nTest Results: ${passed} passed, ${failed} failed");

if (failed > 0) {
script.exit(1);
}

Continuous Integration

# .github/workflows/test.yml
name: Test Utah Scripts

on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Install Utah
run: |
curl -sL https://utahshx.com/install.sh | sudo bash

- name: Run Tests
run: |
utah test/run-tests.shx

Mocking and Stubbing

Mocking External Dependencies

// test/mocks.shx
let mockApiResponse: string = "{\"status\": \"success\", \"data\": {\"id\": 1}}";

function mockWebGet(url: string): string {
console.log("Mock: GET ${url}");
return mockApiResponse;
}

// Override web.get for testing
web.get = mockWebGet;

Stubbing System Commands

// test/stubs.shx
function stubSystemExecute(command: string): string {
if (command == "git status") {
return "On branch main\nnothing to commit, working tree clean";
}
return "Command not found";
}

// Override system.execute for testing
system.execute = stubSystemExecute;

Performance Testing

Benchmarking Functions

// test/performance.test.shx
function benchmarkFunction(func: Function, iterations: number): number {
let startTime: number = utility.timestamp();

for (let i: number = 0; i < iterations; i++) {
func();
}

let endTime: number = utility.timestamp();
return endTime - startTime;
}

function testPerformance(): void {
let executionTime: number = benchmarkFunction(() => {
// Function to benchmark
utility.hash("test string");
}, 1000);

console.log("Execution time: ${executionTime}ms");
assert(executionTime < 5000, "Function should execute in under 5 seconds");
}

Best Practices

Test File Organization

  • Keep tests in a separate test/ directory
  • Use descriptive test function names
  • Group related tests together
  • Use setup and teardown functions for common operations

Test Coverage

  • Test normal cases and edge cases
  • Test error conditions and exception handling
  • Test boundary conditions
  • Test integration points

Test Data Management

  • Use temporary files for file system tests
  • Clean up test data after each test
  • Use consistent test data across tests
  • Avoid dependencies on external services in unit tests

Assertions and Reporting

  • Use clear assertion messages
  • Provide detailed error information
  • Log test progress and results
  • Exit with appropriate codes for CI/CD integration