> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usebruno.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing

<Warning>
  Bruno uses the [Chai library <strong><sup>↗</sup></strong>](https://www.chaijs.com/) for assertions. All Chai expect syntax works in Bruno tests.
</Warning>

Write JavaScript test scripts to validate API responses, handle complex logic, and automate testing workflows.

<img src="https://mintcdn.com/bruno-a6972042/K-YsMv4Crp6p2uFR/images/screenshots/get-started/bruno-basics/create_test/test-script.webp?fit=max&auto=format&n=K-YsMv4Crp6p2uFR&q=85&s=00d1c2664b27ecc46efcd3314f128ccb" alt="bru test script" width="2480" height="1046" data-path="images/screenshots/get-started/bruno-basics/create_test/test-script.webp" />

## Basic Test Structure

```javascript theme={null}
test("test name", function () {
  expect(res.getStatus()).to.equal(200);
});
```

## Common Test Examples

### Testing Status Codes

```javascript theme={null}
test("should return success", function () {
  expect(res.getStatus()).to.equal(200);
});

test("should not return server error", function () {
  expect(res.getStatus()).to.not.equal(500);
});
```

### Testing Response Body

```javascript theme={null}
test("should return user data", function () {
  const body = res.getBody();
  
  expect(body).to.have.property("id");
  expect(body.name).to.equal("John Doe");
  expect(body.email).to.contain("@example.com");
});

test("should return array of users", function () {
  const users = res.getBody();
  
  expect(users).to.be.an("array");
  expect(users).to.have.lengthOf(3);
  expect(users[0]).to.have.property("id");
});
```

### Validating the JSON Body with `jsonBody`

Bruno provides a custom Chai assertion `jsonBody` to make response-body checks more readable. It's the native Bruno equivalent of Postman's `pm.response.to.have.jsonBody(...)`, and supports four call shapes:

```javascript theme={null}
test("body is JSON", function () {
  expect(res.getBody()).to.have.jsonBody();
});

test("body deep-equals an object", function () {
  expect(res.getBody()).to.have.jsonBody({ id: 1, name: "Alice" });
});

test("a nested path exists", function () {
  expect(res.getBody()).to.have.jsonBody("user.id");
});

test("a nested path equals a value", function () {
  expect(res.getBody()).to.have.jsonBody("user.id", 123);
});
```

Negation is supported across all variants:

```javascript theme={null}
test("body is not the legacy shape", function () {
  expect(res.getBody()).to.not.have.jsonBody({ legacy: true });
});

test("debug field is not exposed", function () {
  expect(res.getBody()).to.not.have.jsonBody("internal.debugInfo");
});
```

<Tip>
  **Migrating from Postman?** When you import a Postman collection, Bruno automatically translates `pm.response.to.have.jsonBody(...)` (and `pm.response.to.not.have.jsonBody(...)`) into `expect(res.getBody()).to.have.jsonBody(...)`. No manual rewriting needed.
</Tip>

### Validating against a JSON Schema with `jsonSchema`

For contract testing, use the `jsonSchema` assertion to validate a response against a [JSON Schema](https://json-schema.org/) document. Bruno uses [Ajv](https://ajv.js.org/) under the hood, so all modern JSON Schema drafts are supported (**04, 06, 07, 2019-09, 2020-12**) along with the full [ajv-formats](https://ajv.js.org/packages/ajv-formats.html) library.

```javascript theme={null}
test("response matches the user schema", function () {
  const userSchema = {
    type: "object",
    required: ["id", "email"],
    properties: {
      id: { type: "integer" },
      email: { type: "string", format: "email" },
      createdAt: { type: "string", format: "date-time" },
      website: { type: "string", format: "uri" }
    }
  };

  expect(res.getBody()).to.have.jsonSchema(userSchema);
});
```

#### Built-in formats

Common `ajv-formats` validators are available out of the box, including `email`, `date`, `date-time`, `time`, `uri`, `uri-reference`, `uuid`, `ipv4`, `ipv6`, `hostname`, and `regex`.

```javascript theme={null}
test("user has a valid email and signup date", function () {
  expect(res.getBody()).to.have.jsonSchema({
    type: "object",
    properties: {
      email: { type: "string", format: "email" },
      signupDate: { type: "string", format: "date-time" }
    }
  });
});
```

#### Passing Ajv options

You can pass any [Ajv options](https://ajv.js.org/options.html) as a second argument — useful, for example, to collect every validation error rather than failing on the first one:

```javascript theme={null}
test("collect every schema violation", function () {
  expect(res.getBody()).to.have.jsonSchema(userSchema, { allErrors: true });
});
```

#### Negation

```javascript theme={null}
test("response should not match the legacy v1 schema", function () {
  expect(res.getBody()).to.not.have.jsonSchema(legacyV1Schema);
});
```

<Info>
  Like `jsonBody`, the `jsonSchema` assertion runs in both the **Safe Mode** and the **Developer Mode**, and Postman's `pm.response.to.have.jsonSchema(schema)` is automatically translated to `expect(res.getBody()).to.have.jsonSchema(schema)` on import.
</Info>

<Warning>
  Earlier versions of Bruno used `tv4` for schema validation, which only supports JSON Schema **draft-04**. The new Ajv-based `jsonSchema` assertion is the recommended way to validate schemas going forward and supports drafts **04, 06, 07, 2019-09, and 2020-12**.
</Warning>

### Testing Nested Objects

```javascript theme={null}
test("should validate nested user profile", function () {
  const body = res.getBody();
  
  expect(body.user.profile.name).to.equal("Alice");
  expect(body.user.settings.theme).to.equal("dark");
  expect(body.user.settings.notifications).to.be.true;
});
```

### Testing with Conditional Logic

```javascript theme={null}
test("should validate response based on status", function () {
  const status = res.getStatus();
  const body = res.getBody();
  
  if (status === 200) {
    expect(body).to.have.property("data");
    expect(body.data).to.not.be.empty;
  } else if (status === 404) {
    expect(body).to.have.property("error");
    expect(body.error.message).to.contain("not found");
  } else {
    throw new Error(`Unexpected status code: ${status}`);
  }
});

test("should validate user role permissions", function () {
  const body = res.getBody();
  
  if (body.user.role === "admin") {
    expect(body.user.permissions).to.include("write");
    expect(body.user.permissions).to.include("delete");
  } else if (body.user.role === "user") {
    expect(body.user.permissions).to.include("read");
    expect(body.user.permissions).to.not.include("delete");
  }
});
```

### Testing Arrays and Loops

```javascript theme={null}
test("should validate all users have required fields", function () {
  const users = res.getBody();
  
  users.forEach((user) => {
    expect(user).to.have.property("id");
    expect(user).to.have.property("email");
    expect(user.email).to.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/);
  });
});

test("should find user by id", function () {
  const users = res.getBody();
  const targetUser = users.find((u) => u.id === 123);
  
  expect(targetUser).to.exist;
  expect(targetUser.name).to.equal("John");
});
```

### Testing Response Headers

```javascript theme={null}
test("should have correct content type", function () {
  expect(res.getHeader("content-type")).to.contain("application/json");
});

test("should include authentication headers", function () {
  expect(res.getHeader("x-api-key")).to.exist;
  expect(res.getHeader("authorization")).to.not.be.empty;
});
```

### Testing Response Time

```javascript theme={null}
test("should respond quickly", function () {
  expect(res.getResponseTime()).to.be.lessThan(1000);
});
```

### Advanced: Saving Values for Next Request

```javascript theme={null}
test("should save token for next request", function () {
  const body = res.getBody();
  
  expect(body).to.have.property("token");
  expect(body.token).to.not.be.empty;
  
  // Save token to environment variable
  bru.setVar("authToken", body.token);
});

test("should extract and save user ID", function () {
  const body = res.getBody();
  
  if (body.users && body.users.length > 0) {
    const firstUserId = body.users[0].id;
    bru.setVar("userId", firstUserId);
  }
});
```

### Error Handling

```javascript theme={null}
test("should handle missing fields gracefully", function () {
  const body = res.getBody();
  
  if (!body.data) {
    expect(body).to.have.property("error");
    expect(body.error.code).to.be.oneOf([400, 404, 422]);
  }
});

test("should validate error response structure", function () {
  const status = res.getStatus();
  
  if (status >= 400) {
    const body = res.getBody();
    expect(body).to.have.property("error");
    expect(body.error).to.have.property("message");
    expect(body.error.message).to.be.a("string");
  }
});
```

## Common Chai Assertions

```javascript theme={null}
// Equality
expect(value).to.equal(expected);
expect(value).to.not.equal(expected);
expect(value).to.eql(expected); // deep equality

// Type checking
expect(value).to.be.a("string");
expect(value).to.be.an("array");
expect(value).to.be.true;
expect(value).to.be.null;

// Property checks
expect(obj).to.have.property("key");
expect(obj).to.have.all.keys("name", "email");

// String checks
expect(str).to.contain("substring");
expect(str).to.match(/regex/);

// Number comparisons
expect(num).to.be.above(10);
expect(num).to.be.below(100);
expect(num).to.be.within(10, 100);

// Array checks
expect(arr).to.be.an("array");
expect(arr).to.have.lengthOf(3);
expect(arr).to.include("item");
expect(arr).to.be.empty;

// Bruno-specific: JSON body and schema validation
expect(res.getBody()).to.have.jsonBody();                  // body is JSON
expect(res.getBody()).to.have.jsonBody({ id: 1 });         // deep-equals
expect(res.getBody()).to.have.jsonBody("user.id");         // path exists
expect(res.getBody()).to.have.jsonBody("user.id", 123);    // path equals value
expect(res.getBody()).to.have.jsonSchema(schema);          // validate via Ajv
expect(res.getBody()).to.have.jsonSchema(schema, { allErrors: true });
```

## Next Steps

For more advanced scripting capabilities, see:

* [Scripting API](../script/javascript-reference) - Full API reference
* [Response Query](../script/response/response-query) - Query complex response data
* [Variables](../script/vars) - Work with variables across requests
