Testing
The testing framework is currently still in development
Testing N1 Applications with Jest
This guide explains how to write effective tests for your N1 applications using Jest and the N1 testing utilities.
Overview
The N1 framework provides testing utilities that allow you to simulate the execution of your N1 applications in a controlled environment. These utilities, combined with Jest, enable you to verify that your application behaves as expected without deploying it to the blockchain.
Setup
To test your N1 applications, you’ll need to install the following dependencies:
npm install --save-dev jest @n1xyz/nts-compiler
Add the following to your package.json
:
"scripts": {
"test": "jest"
}
Basic Test Structure
Here’s the basic structure of a test file for an N1 application:
import { compileCodeAndIdl, MockDB, MockNAppClient } from "@n1xyz/nts-compiler";
describe('YourApp Tests', () => {
// Your application code (or import it)
const testCode = `
import { NApp, NMap, createExecutableFunctions } from '@n1xyz/nts-compiler';
// Your app code here...
`;
let client: any;
const testSigner = 'test_signer';
const testAdmin = 'test_admin';
const testAppId = 'test_app';
beforeAll(async () => {
let mockDB = new MockDB();
const { code, idl } = compileCodeAndIdl(testCode);
client = MockNAppClient.loadClientFromCodeAndIDL(code, idl, {
signer: testSigner,
appAdmin: testAdmin,
appId: testAppId,
db: mockDB,
});
});
test('should do something', async () => {
// Your test here
});
});
Testing Function Execution
To test the execution of a function in your N1 application:
test('should successfully set a message', async () => {
const testMessage = 'Hello, World!';
const result = await client.executeAction('setMessage', [testMessage]);
// Verify the write operation
const messageWrite = result.transactionWrites.find((write: any) =>
write.fieldId === `messages____${testSigner}`
);
expect(messageWrite).toBeTruthy();
expect(messageWrite.value).toBe(testMessage);
// Verify we can read the message from the database
const storedMessage = await client.readField(`messages____${testSigner}`);
expect(storedMessage).toBe(testMessage);
});
Testing Validation Rules
To test validation rules and error handling:
test('should fail when setting an empty message', async () => {
// Test with empty string
await expect(
client.executeAction('setMessage', [''])
).rejects.toThrow('Message cannot be empty');
// Test with whitespace only
await expect(
client.executeAction('setMessage', [' '])
).rejects.toThrow('Message cannot be empty');
});
Testing State Updates
To test that your application correctly updates its state:
test('should update existing message', async () => {
const initialMessage = 'Initial message';
const updatedMessage = 'Updated message';
// Set initial message
await client.executeAction('setMessage', [initialMessage]);
// Update the message
const result = await client.executeAction('setMessage', [updatedMessage]);
// Verify the write operation
const messageWrite = result.transactionWrites.find((write: any) =>
write.fieldId === `messages____${testSigner}`
);
expect(messageWrite).toBeTruthy();
expect(messageWrite.value).toBe(updatedMessage);
// Verify the updated message in the database
const storedMessage = await client.readField(`messages____${testSigner}`);
expect(storedMessage).toBe(updatedMessage);
});
Testing Token Operations
For applications that involve token operations like minting:
test('should successfully create a token', async () => {
const testName = 'Hello, World!';
const testSymbol = 'HW';
const testSupply = 1000;
const result = await client.executeAction('createMint', [testName, testSymbol, testSupply]);
// Verify the write operation
const mintWrite = result.transactionWrites.find((write: any) =>
write.fieldId === `mints____${testSymbol}`
);
expect(mintWrite).toBeTruthy();
expect(JSON.parse(mintWrite.value).meta.name).toBe(testName);
// Verify we can read the message from the database
const storedMint = JSON.parse(await client.readField(`mints____${testSymbol}`));
expect(storedMint.meta.name).toBe(testName);
expect(storedMint.meta.symbol).toBe(testSymbol);
expect(storedMint.amount).toBe(testSupply);
// Verify balance
const balance = Number(await client.readField(`user_balance_${storedMint.mint}_${testSigner}`));
expect(balance).toBe(testSupply);
});
Best Practices
-
Test Isolation: Each test should be independent and not rely on the state from previous tests.
-
Test Edge Cases: Include tests for edge cases and error conditions.
-
Clear Test Names: Use descriptive test names that explain what is being tested.
-
Arrange-Act-Assert: Structure your tests with a clear setup, action, and verification phases.
-
Test Data Management: Use constant values for test data to make tests more predictable.
API Reference
MockDB
A mock database that simulates the blockchain’s storage.
const mockDB = new MockDB();
compileCodeAndIdl
Compiles your N1 application code and generates the IDL (Interface Definition Language).
const { code, idl } = compileCodeAndIdl(yourAppCode);
MockNAppClient
Creates a client that can interact with your mock N1 application.
const client = MockNAppClient.loadClientFromCodeAndIDL(code, idl, {
signer: testSigner,
appAdmin: testAdmin,
appId: testAppId,
db: mockDB,
});
client.executeAction
Executes an action (function) in your application.
const result = await client.executeAction('functionName', [param1, param2]);
client.readField
Reads a field from the mock database.
const value = await client.readField('fieldId');
result.transactionWrites
An array of write operations performed during function execution.
const write = result.transactionWrites.find(w => w.fieldId === 'yourFieldId');