Skip to Content
DevelopersNTSTesting

Testing

️⚠️
NTS is in active development and currently unstable. It is open to select early developers for exploration but is not yet suitable for production use. Follow developer updates for the latest changes, as performance is not at production level.

Testing N1 Applications

This guide explains how to write tests for your N1 applications using the built-in client testing system.

Overview

N1 provides a simple testing system that allows you to test your applications using the same client interface that external users would use. No additional testing libraries are required.

Tests in N1 are simple TypeScript functions that use the NAppClient to interact with your application, just like a real user would.

Basic Test Structure

Create Test File

Create a test file that exports a default async function:

tests/counter.test.ts
import { NAppClient } from "@n1xyz/nts-sdk"; import { ContractIDL } from "../build/idl"; export default async function ({ client }: { client: NAppClient<ContractIDL> }) { await client.actions.init(); await client.actions.increment(2); const count = await client.state.count(); console.log('Current count:', count); }

Define Your App

Your application code:

src/index.ts
import { NApp, createExecutableFunctions } from '@n1xyz/nts-compiler'; class Counter extends NApp { count: number; init(): void { this.count = 0; } increment(amount: number): void { this.count += amount; } decrement(amount: number): void { this.count -= amount; } } export const { init, increment, decrement } = createExecutableFunctions(Counter);

Build and Test

The build system generates the IDL and provides the client interface for testing.

Writing Tests

Test Actions

Use client.actions to call your application’s transaction actions:

tests/example.test.ts
import { NAppClient } from "@n1xyz/nts-sdk"; import { ContractIDL } from "../build/idl"; export default async function ({ client }: { client: NAppClient<ContractIDL> }) { // Initialize the app await client.actions.init(); // Call increment action await client.actions.increment(5); // Call decrement action await client.actions.decrement(2); // Read final state const finalCount = await client.state.count(); console.log('Final count:', finalCount); // Should be 3 }

Test State Reading

Use client.state to read your application’s state:

tests/state.test.ts
import { NAppClient } from "@n1xyz/nts-sdk"; import { ContractIDL } from "../build/idl"; export default async function ({ client }: { client: NAppClient<ContractIDL> }) { // Initialize app await client.actions.init(); // Check initial state const initialCount = await client.state.count(); console.log('Initial count:', initialCount); // Should be 0 // Perform operations await client.actions.increment(10); await client.actions.decrement(3); // Check final state const finalCount = await client.state.count(); console.log('Final count:', finalCount); // Should be 7 }

Testing Complex Applications

Token Application Test

tests/token-app.test.ts
import { NAppClient } from "@n1xyz/nts-sdk"; import { ContractIDL } from "../build/idl"; export default async function ({ client }: { client: NAppClient<ContractIDL> }) { // Initialize the token app await client.actions.init(); // Create a token await client.actions.createToken( '1000000', // Total supply 'My Game Token' // Metadata ); // Transfer tokens to another user await client.actions.transferTokens( 'recipient_address', 'mint_id_from_creation', '1000' ); // Check app state const totalSupply = await client.state.totalSupply(); console.log('Total supply:', totalSupply); const userCount = await client.state.userCount(); console.log('User count:', userCount); }

Multi-User Testing

tests/multi-user.test.ts
import { NAppClient } from "@n1xyz/nts-sdk"; import { ContractIDL } from "../build/idl"; export default async function ({ client }: { client: NAppClient<ContractIDL> }) { // Test with different signers await client.actions.init(); // Register first user await client.actions.registerUser('Alice', 'alice@example.com'); // Check user registration const userCount = await client.state.userCount(); console.log('Users registered:', userCount); // Additional operations... await client.actions.updateUserProfile('Alice Updated'); // Verify state changes const isActive = await client.state.isActive(); console.log('App is active:', isActive); }

Testing Patterns

Error Handling Tests

tests/error-handling.test.ts
import { NAppClient } from "@n1xyz/nts-sdk"; import { ContractIDL } from "../build/idl"; export default async function ({ client }: { client: NAppClient<ContractIDL> }) { await client.actions.init(); try { // This should fail await client.actions.transferTokens('', 'invalid_mint', '0'); console.log('ERROR: Should have thrown an error'); } catch (error) { console.log('✅ Correctly caught error:', error.message); } try { // This should also fail await client.actions.increment(-5); // Negative increment console.log('ERROR: Should have thrown an error'); } catch (error) { console.log('✅ Correctly caught error:', error.message); } }

Validation Tests

tests/validation.test.ts
import { NAppClient } from "@n1xyz/nts-sdk"; import { ContractIDL } from "../build/idl"; export default async function ({ client }: { client: NAppClient<ContractIDL> }) { await client.actions.init(); // Test valid operations await client.actions.setMessage('Valid message'); const message = await client.state.message(); console.log('✅ Valid message set:', message); // Test edge cases await client.actions.setMessage('A'.repeat(1000)); // Long message const longMessage = await client.state.message(); console.log('✅ Long message length:', longMessage.length); // Test boundary conditions await client.actions.increment(Number.MAX_SAFE_INTEGER - 1000); const largeCount = await client.state.count(); console.log('✅ Large count handled:', largeCount); }

Test Organization

Comprehensive Test Suite

tests/comprehensive.test.ts
import { NAppClient } from "@n1xyz/nts-sdk"; import { ContractIDL } from "../build/idl"; export default async function ({ client }: { client: NAppClient<ContractIDL> }) { console.log('🧪 Starting comprehensive test suite...\n'); // Test 1: Initialization console.log('📝 Test 1: Initialization'); await client.actions.init(); const initialState = await client.state.count(); console.log(`✅ Initial count: ${initialState}\n`); // Test 2: Basic Operations console.log('📝 Test 2: Basic Operations'); await client.actions.increment(10); await client.actions.decrement(3); const afterOps = await client.state.count(); console.log(`✅ After operations: ${afterOps}\n`); // Test 3: Edge Cases console.log('📝 Test 3: Edge Cases'); await client.actions.increment(0); // Zero increment const afterZero = await client.state.count(); console.log(`✅ After zero increment: ${afterZero}\n`); // Test 4: State Consistency console.log('📝 Test 4: State Consistency'); const stateSnapshot = await client.state.count(); await client.actions.increment(5); await client.actions.decrement(5); const stateAfterRoundTrip = await client.state.count(); if (stateSnapshot === stateAfterRoundTrip) { console.log('✅ State consistency maintained\n'); } else { console.log('❌ State consistency failed\n'); } console.log('🎉 Test suite completed!'); }

Best Practices

1. Clear Test Flow

// ✅ Good: Clear test flow with logging export default async function ({ client }: { client: NAppClient<ContractIDL> }) { console.log('Starting test: Token Creation'); await client.actions.init(); console.log('✅ App initialized'); await client.actions.createToken('1000', 'Test Token'); console.log('✅ Token created'); const supply = await client.state.totalSupply(); console.log(`✅ Total supply: ${supply}`); }

2. Test Real Scenarios

// ✅ Good: Test realistic user flows export default async function ({ client }: { client: NAppClient<ContractIDL> }) { // Simulate real user registration flow await client.actions.init(); await client.actions.registerUser('John Doe', 'john@example.com'); await client.actions.updateUserProfile('Updated John Doe'); // Verify the complete flow worked const userCount = await client.state.userCount(); const isActive = await client.state.isActive(); console.log(`Users: ${userCount}, Active: ${isActive}`); }

3. Test Error Conditions

// ✅ Good: Test error conditions export default async function ({ client }: { client: NAppClient<ContractIDL> }) { await client.actions.init(); // Test various error conditions const errorTests = [ () => client.actions.transferTokens('', 'invalid', '0'), () => client.actions.registerUser('', ''), () => client.actions.increment(-1) ]; for (const test of errorTests) { try { await test(); console.log('❌ Should have thrown error'); } catch (error) { console.log('✅ Error correctly handled:', error.message); } } }

4. Use Descriptive Logging

// ✅ Good: Descriptive logging for debugging export default async function ({ client }: { client: NAppClient<ContractIDL> }) { console.log('🚀 Testing message system functionality'); await client.actions.init(); console.log('📋 App initialized successfully'); await client.actions.setMessage('Hello, N1!'); console.log('💬 Message set successfully'); const message = await client.state.message(); console.log(`📖 Retrieved message: "${message}"`); console.log('✨ Test completed successfully'); }

Running Tests

Tests are executed as part of your N1 application’s build and deployment process. The testing system automatically:

  1. Compiles your application code
  2. Generates the IDL interface
  3. Creates a test client
  4. Executes your test functions
  5. Reports results
⚠️

Remember that tests run against your actual application logic, so they validate the real behavior that users will experience.

Last updated on