# Testing your agent

{% hint style="success" %}
This is the official documentation of the `@forestadmin/agent` Node.js agent.
{% endhint %}

## Testing Your Agent

Test your Forest Admin customizations locally without connecting to Forest Admin servers.

### Installation

```bash
npm install --save-dev @forestadmin/agent-testing
```

### Quick Start

The testing library provides two main functions:

* **`createForestServerSandbox(port)`** - Starts a local mock server that simulates Forest Admin servers.
* **`createAgentTestClient(options)`** - Creates a test client to interact with your agent.

```typescript
import {
  createAgentTestClient,
  createForestServerSandbox,
} from '@forestadmin/agent-testing';

describe('My Agent', () => {
  let sandbox, client;

  beforeAll(async () => {
    // Start mock server
    sandbox = await createForestServerSandbox(3001);
    // Connect test client
    client = await createAgentTestClient({
      serverUrl: 'http://localhost:3001',
      agentUrl: 'http://localhost:3310',
      agentSchemaPath: './.forestadmin-schema.json',
      agentForestEnvSecret: process.env.FOREST_ENV_SECRET,
      agentForestAuthSecret: process.env.FOREST_AUTH_SECRET,
    });
  });

  afterAll(async () => {
    await sandbox?.stop();
  });

  it('should list users', async () => {
    const users = await client.collection('users').list();
    expect(users.length).toBeGreaterThan(0);
  });

  it('should execute action', async () => {
    const action = await client
      .collection('orders')
      .action('Apply discount', { recordId: 1 });
    await action.getFieldNumber('discount').fill(10);
    const result = await action.execute();
    expect(result.success).toBe(true);
  });
});
```

### Collections

Test CRUD operations and hooks.

#### List

Test that filters return the correct records.

```typescript
// Fetch admins
const admins = await client.collection('users').list({
  filters: { field: 'role', operator: 'Equal', value: 'admin' },
});
// Check all have role 'admin'
expect(admins.every(u => u.role === 'admin')).toBe(true);
```

#### Create

Test that a record is correctly created.

```typescript
// Create user
const user = await client.collection('users').create({ email: 'john@example.com' });
// Fetch to verify
const [created] = await client.collection('users').list({
  filters: { field: 'id', operator: 'Equal', value: user.id },
});
// Check record exists
expect(created.email).toBe('john@example.com');
```

#### Update

Test that a record is correctly updated.

```typescript
// Update email
await client.collection('users').update(userId, { email: 'new@example.com' });
// Fetch to verify
const [user] = await client.collection('users').list({
  filters: { field: 'id', operator: 'Equal', value: userId },
});
// Check new value
expect(user.email).toBe('new@example.com');
```

#### Delete

Test that a record is correctly deleted.

```typescript
// Delete user
await client.collection('users').delete(userId);
// Fetch to verify
const users = await client.collection('users').list({
  filters: { field: 'id', operator: 'Equal', value: userId },
});
// Check record is gone
expect(users).toHaveLength(0);
```

### Actions

Test smart actions and form behavior.

```typescript
// Get action
const action = await client.collection('orders').action('Refund', { recordId: 1 });
// Fill form
await action.getFieldNumber('amount').fill(100);
await action.getFieldString('reason').fill('Customer request');
// Execute
const result = await action.execute();
// Check success
expect(result.success).toBe(true);
```

#### Dynamic forms

Test fields that appear based on other field values.

```typescript
const action = await client.collection('orders').action('Refund', { recordId: 1 });
// Check field is hidden
expect(action.doesFieldExist('manager_approval')).toBe(false);
// Fill amount > threshold
await action.getFieldNumber('amount').fill(500);
// Check field now appears
expect(action.doesFieldExist('manager_approval')).toBe(true);
```

#### Field properties

Test field validation rules.

```typescript
const action = await client.collection('users').action('Update', { recordId: 1 });
// Check required
expect(action.getFieldString('email').isRequired()).toBe(true);
// Check read-only
expect(action.getFieldNumber('age').isReadOnly()).toBe(false);
```

### Computed Fields

Test custom computed fields.

```typescript
// Fetch user
const [user] = await client.collection('users').list<{ fullName: string }>();
// Check computed value
expect(user.fullName).toBe('John Doe');
```

### Segments

Test custom segments.

```typescript
// Fetch from segment
const minors = await client.collection('users').segment('minors').list();
// Check filter works
expect(minors.every(u => u.age < 18)).toBe(true);
```

### Charts

Test dashboard charts.

```typescript
// Fetch value chart
const chart = await client.valueChart('totalRevenue');
// Check returns data
expect(chart.countCurrent).toBeGreaterThan(0);

// Fetch distribution chart
const distribution = await client.distributionChart('ordersByStatus');
// Check contains expected key
expect(distribution).toContainEqual({ key: 'pending', value: expect.any(Number) });
```
