Skip to main content
Bruno includes a comprehensive testing framework that allows you to write JavaScript test assertions to validate API responses. Tests are managed by the Tests component and use a Chai-based assertion library.

Overview

Tests in Bruno are JavaScript code snippets that run after receiving an API response. They use the same execution environment as scripts but are specifically designed for assertions and validations.

Tests vs Assertions

Bruno provides two ways to validate responses:
JavaScript test suite using Chai assertions. Managed by the Tests component.
test("Status should be 200", function() {
  expect(res.status).to.equal(200);
});

test("Response should have users array", function() {
  expect(res.body.users).to.be.an('array');
  expect(res.body.users.length).to.be.greaterThan(0);
});
Use the Assert tab for simple, declarative assertions. Use the Tests tab for complex validations, loops, and conditional logic.

Test Editor

The Tests component provides a CodeMirror editor with:
  • JavaScript syntax highlighting
  • Auto-hints for req, res, and bru objects
  • Keyboard shortcuts (Cmd/Ctrl+Enter to run, Cmd/Ctrl+S to save)
  • Error indicators in the Tests tab
From RequestPane/Tests/index.js:
<CodeEditor
  collection={collection}
  value={tests || ''}
  theme={displayedTheme}
  font={get(preferences, 'font.codeFont', 'default')}
  fontSize={get(preferences, 'font.codeFontSize')}
  onEdit={onEdit}
  mode="javascript"
  onRun={onRun}
  onSave={onSave}
  showHintsFor={['req', 'res', 'bru']}
/>

Available Objects in Tests

res
object
Response object containing:
  • res.status: HTTP status code (e.g., 200, 404)
  • res.statusText: Status text (e.g., “OK”, “Not Found”)
  • res.headers: Response headers object
  • res.body: Parsed response body (JSON object/array)
  • res.getHeader(name): Get specific header value
  • res.getBody(): Get raw response body
req
object
Request object (read-only in tests):
  • req.url: Request URL
  • req.method: HTTP method
  • req.headers: Request headers
  • req.body: Request body
bru
object
Bruno utility object:
  • bru.getVar(name): Get collection variable
  • bru.setVar(name, value): Set collection variable
  • bru.getEnvVar(name): Get environment variable
  • bru.setEnvVar(name, value): Set environment variable

Chai Assertion Library

Bruno tests use Chai for assertions. The expect and test functions are globally available.

Basic Assertions

test("Status code is 200", function() {
  expect(res.status).to.equal(200);
});

test("Response message matches", function() {
  expect(res.body.message).to.equal("Success");
});

test("Deep equality", function() {
  expect(res.body.user).to.deep.equal({
    id: 123,
    name: "John Doe"
  });
});

Advanced Assertions

test("Response has required properties", function() {
  expect(res.body).to.have.property('id');
  expect(res.body).to.have.property('name');
  expect(res.body).to.have.property('email');
});

test("User has all properties", function() {
  expect(res.body.user).to.have.all.keys('id', 'name', 'email', 'role');
});

test("Nested property exists", function() {
  expect(res.body).to.have.nested.property('user.address.city');
});

Real-World Examples

From the Bruno test suite:

Form URL Encoded Test

bruno-tests/collection/echo/echo form-url-encoded.bru
tests {
  test("form-urlencoded body with variables and duplicate keys", function() {
    const expected = [
      "form-data-key=form-data-value",
      "form-data-stringified-object=%7B%22foo%22%3A123%7D", // {"foo":123} URL encoded
      "key_1=value_1",
      "key_2=value_2", 
      "key_1=value_3", // duplicate key with different value
      "key_2=value_4"  // duplicate key with different value
    ].join("&");
    
    expect(res.getBody()).to.eql(expected);
  });
}

Collection-Level Test

From collection.bru:
tests {
  const shouldTestCollectionScripts = bru.getVar('should-test-collection-scripts');
  const collectionVar = bru.getVar("collection-var-set-by-collection-script");
  
  if (shouldTestCollectionScripts && collectionVar) {
    test("collection level test - should get the var that was set by the collection script", function() {
      expect(collectionVar).to.equal("collection-var-value-set-by-collection-script");
    }); 
    
    bru.setVar('collection-var-set-by-collection-script', null); 
    bru.setVar('should-test-collection-scripts', null);
  }
}

Assert Combinations Test

From bruno-tests/collection/asserts/test-assert-combinations.bru showing various assertion patterns:
assert {
  res.body.string: eq foo
  res.body.string: eq 'foo'
  res.body.string: eq "foo"
  res.body.number: eq 123
  res.body.numberAsString: eq '123'
  res.body.numberBig.toString(): eq '9007199254740992000'
  res.body.null: eq null
  res.body.nullAsString: eq "null"
  res.body.true: eq true
  res.body.trueAsString: eq "true"
  res.body.false: eq false
  res.body.nonexistent: eq undefined
  res.body.stringWithCurlyBraces: eq "{foo}"
  res.body.stringWithDoubleCurlyBraces: eq "{{foobar}}"
}

Common Testing Patterns

test("Response is successful (2xx)", function() {
  expect(res.status).to.be.at.least(200);
  expect(res.status).to.be.below(300);
});
test("User object has correct schema", function() {
  expect(res.body).to.be.an('object');
  expect(res.body).to.have.property('id').that.is.a('number');
  expect(res.body).to.have.property('name').that.is.a('string');
  expect(res.body).to.have.property('email').that.is.a('string');
  expect(res.body).to.have.property('created_at').that.is.a('string');
});
test("Pagination data is valid", function() {
  expect(res.body).to.have.property('pagination');
  expect(res.body.pagination.page).to.equal(1);
  expect(res.body.pagination.per_page).to.equal(10);
  expect(res.body.pagination.total).to.be.a('number');
  expect(res.body.data).to.be.an('array');
  expect(res.body.data.length).to.be.at.most(10);
});
test("Email format is valid", function() {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  expect(res.body.email).to.match(emailRegex);
});
test("All users have required fields", function() {
  expect(res.body.users).to.be.an('array');
  expect(res.body.users.length).to.be.greaterThan(0);
  
  res.body.users.forEach(user => {
    expect(user).to.have.property('id');
    expect(user).to.have.property('name');
    expect(user).to.have.property('email');
  });
});
test("Response structure varies by type", function() {
  if (res.body.type === 'user') {
    expect(res.body).to.have.property('email');
    expect(res.body).to.have.property('role');
  } else if (res.body.type === 'organization') {
    expect(res.body).to.have.property('domain');
    expect(res.body).to.have.property('members');
  }
});
test("Error response has correct structure", function() {
  expect(res.status).to.be.at.least(400);
  expect(res.body).to.have.property('error');
  expect(res.body.error).to.have.property('message');
  expect(res.body.error).to.have.property('code');
});

Test Storage

Tests are stored in the .bru file:
tests {
  test("Status should be 200", function() {
    expect(res.status).to.equal(200);
  });
  
  test("Response has users", function() {
    expect(res.body.users).to.be.an('array');
  });
}
The Redux store manages test state through updateRequestTests action.

Test Execution & Results

When you send a request:
1

Request Executes

Bruno sends the HTTP request using the Axios client.
2

Response Received

Response data is captured and made available as res object.
3

Tests Run

Tests execute in order. Each test() function runs independently.
4

Results Displayed

Test results appear in the Response Pane, showing passed/failed tests.

Test Error Indicators

The Tests tab shows error indicators:
From HttpRequestPane component
tests: tests?.length > 0 ? (hasTestError ? <StatusDot type="error" /> : <StatusDot />) : null
Failed tests populate item.testScriptErrorMessage.

Collection Runner Tests

When running multiple requests using the RunCollectionItem component, tests run for each request and results are aggregated in RunnerResults.

Best Practices

Use clear, descriptive names that explain what is being tested:
// Good
test("User creation returns 201 with user ID", function() { ... });

// Bad
test("test1", function() { ... });
Keep tests focused on a single assertion or related group:
// Good - focused
test("Status code is 200", function() {
  expect(res.status).to.equal(200);
});

test("Response has user object", function() {
  expect(res.body.user).to.exist;
});

// Bad - too much in one test
test("Everything works", function() {
  expect(res.status).to.equal(200);
  expect(res.body.user).to.exist;
  expect(res.headers).to.have.property('content-type');
});
Choose the right Chai assertion for readability:
// Good - clear intent
expect(res.body.users).to.be.an('array');
expect(res.body.users).to.not.be.empty;

// Bad - unnecessarily complex
expect(typeof res.body.users === 'object' && Array.isArray(res.body.users)).to.be.true;
Test happy paths and error scenarios:
// Success case
test("Valid request returns 200", function() {
  expect(res.status).to.equal(200);
});

// Error case (in separate request)
test("Invalid ID returns 404 with error", function() {
  expect(res.status).to.equal(404);
  expect(res.body.error).to.exist;
});
Put repeated validations in collection-level tests:
collection.bru tests
tests {
  // Run for all requests in collection
  test("Response time is acceptable", function() {
    expect(res.responseTime).to.be.below(1000);
  });
  
  test("Content-Type is JSON", function() {
    expect(res.getHeader('content-type')).to.include('application/json');
  });
}

Next Steps

Assertions

Learn about inline assertions using the Assert tab

Scripts

Combine tests with pre/post-request scripts

Collection Runner

Run tests across multiple requests

Collection Settings

Configure collection-level tests

Build docs developers (and LLMs) love