Skip to main content
Bruno provides a comprehensive assertion system for validating API responses. Use assertions to verify status codes, check response data, validate schemas, and ensure your APIs behave correctly.

Assertion Methods

Bruno supports two ways to write assertions:

Inline Assertions

Quick assertions in the assert block

Test Scripts

Complex validations using JavaScript and Chai

Inline Assertions

Inline assertions provide a simple, declarative way to validate responses without writing JavaScript.

Basic Syntax

assert {
  res.status: eq 200
  res.body.message: eq Authentication successful
  res.body.user.email: contains @example.com
}

Assertion Operators

Bruno supports a rich set of operators for different validation needs:
eq
operator
Equal to
res.body.string: eq foo
res.body.number: eq 123
res.body.null: eq null
res.body.true: eq true
res.body.false: eq false
neq
operator
Not equal to
res.body.status: neq pending
res.body.count: neq 0
gt
operator
Greater than
res.body.age: gt 18
res.body.score: gt 0
gte
operator
Greater than or equal to
res.body.quantity: gte 1
res.body.percentage: gte 0
lt
operator
Less than
res.body.price: lt 100
res.body.timeout: lt 5000
lte
operator
Less than or equal to
res.body.maxUsers: lte 1000
res.getResponseTime(): lte 2000
in
operator
Value is in list
res.body.status: in active,pending,completed
res.body.type: in [user, admin, guest]
res.body.code: in 200,201,204
notIn
operator
Value is not in list
res.body.status: notIn deleted,archived
res.body.role: notIn [banned, suspended]
contains
operator
String/array contains value
res.body.message: contains success
res.body.email: contains @example.com
res.body: contains form-data-value
notContains
operator
String/array does not contain value
res.body.message: notContains error
res.body.description: notContains deprecated
startsWith
operator
String starts with value
res.body.url: startsWith https://
res.body.prefix: startsWith API_
endsWith
operator
String ends with value
res.body.filename: endsWith .pdf
res.body.email: endsWith @example.com
matches
operator
Matches regular expression
res.body.email: matches ^[^\s@]+@[^\s@]+\.[^\s@]+$
res.body.phone: matches /^\d{3}-\d{3}-\d{4}$/
res.body.uuid: matches [0-9a-f]{8}-[0-9a-f]{4}
notMatches
operator
Does not match regular expression
res.body.username: notMatches \s
res.body.code: notMatches [^a-zA-Z0-9]
length
operator
Length equals value
res.body.items: length 10
res.body.code: length 6
res.body.apiKey: length 32
between
operator
Value is between min and max (inclusive)
res.body.age: between 18,65
res.body.price: between 9.99,99.99
res.getResponseTime(): between 0,2000
isNull
operator
Value is null
res.body.deletedAt: isNull
res.body.optional: isNull
isUndefined
operator
Value is undefined
res.body.nonexistent: isUndefined
isDefined
operator
Value is not undefined
res.body.userId: isDefined
res.body.email: isDefined
isJson
operator
Value is a JSON object
res.body.metadata: isJson
res.body.config: isJson
isNumber
operator
Value is a number
res.body.count: isNumber
res.body.price: isNumber
isString
operator
Value is a string
res.body.name: isString
res.body.email: isString
isBoolean
operator
Value is a boolean
res.body.isActive: isBoolean
res.body.verified: isBoolean
isArray
operator
Value is an array
res.body.items: isArray
res.body.tags: isArray
isEmpty
operator
Value is empty (string, array, or object)
res.body.description: isEmpty
res.body.items: isEmpty
isNotEmpty
operator
Value is not empty
res.body.message: isNotEmpty
res.body.results: isNotEmpty
isTruthy
operator
Value is truthy (true, non-zero number, non-empty string)
res.body.success: isTruthy
isFalsy
operator
Value is falsy (false, 0, empty string, null, undefined)
res.body.error: isFalsy

Real-World Examples

assert {
  res.body.string: eq foo
  res.body.string: eq 'foo'
  res.body.string: eq "foo"
  res.body.stringWithSQuotes: eq "'foo'"
  res.body.stringWithDQuotes: eq '"foo"'
  res.body.stringWithCurlyBraces: eq "{foo}"
  res.body.stringWithDoubleCurlyBraces: eq "{{foobar}}"
}

Test Scripts

For complex validations, use test scripts with the full power of JavaScript and Chai assertion library.

Basic Test Structure

test("test description", function() {
  // Your assertions here
  expect(res.getStatus()).to.equal(200);
  expect(res.getBody()).to.have.property('success', true);
});

Chai Assertions

Bruno includes the Chai assertion library with three assertion styles:
test("validate response structure", function() {
  const data = res.getBody();
  
  // Equality
  expect(res.getStatus()).to.equal(200);
  expect(data.name).to.eql('John Doe');
  
  // Type checking
  expect(data).to.be.an('object');
  expect(data.items).to.be.an('array');
  expect(data.count).to.be.a('number');
  
  // Properties
  expect(data).to.have.property('id');
  expect(data).to.have.property('email', '[email protected]');
  expect(data).to.include.keys('id', 'name', 'email');
  
  // Arrays
  expect(data.roles).to.include('user');
  expect(data.roles).to.have.lengthOf(3);
  expect(data.items).to.be.empty;
  
  // Strings
  expect(data.message).to.contain('success');
  expect(data.email).to.match(/^[^\s@]+@/);
  
  // Numbers
  expect(data.age).to.be.above(18);
  expect(data.score).to.be.within(0, 100);
  
  // Negation
  expect(data.error).to.not.exist;
  expect(data.deleted).to.be.false;
});

Advanced Validation Patterns

test("validate nested object structure", function() {
  const data = res.getBody();
  
  expect(data).to.have.nested.property('user.profile.email');
  expect(data).to.have.nested.property('settings.notifications.email', true);
  
  // Deep equality
  expect(data.user).to.deep.include({
    id: 123,
    role: 'admin'
  });
  
  // Validate entire structure
  expect(data).to.deep.equal({
    success: true,
    user: {
      id: 123,
      name: 'John Doe',
      email: '[email protected]'
    },
    metadata: {
      timestamp: data.metadata.timestamp // dynamic value
    }
  });
});
test("validate array contents", function() {
  const data = res.getBody();
  
  // Array length
  expect(data.items).to.have.lengthOf(10);
  expect(data.items).to.have.length.above(0);
  
  // Array membership
  expect(data.tags).to.include('javascript');
  expect(data.tags).to.include.members(['node', 'api']);
  expect(data.tags).to.have.members(['node', 'api', 'rest']);
  
  // Array of objects
  expect(data.users).to.be.an('array').that.is.not.empty;
  expect(data.users[0]).to.have.property('id');
  
  // Every item matches condition
  data.items.forEach(item => {
    expect(item).to.have.property('id');
    expect(item.id).to.be.a('number');
  });
});
test("response time is acceptable", function() {
  const responseTime = res.getResponseTime();
  
  expect(responseTime).to.be.below(2000);
  expect(responseTime).to.be.within(0, 5000);
});

test("response size is reasonable", function() {
  const size = res.getSize();
  
  expect(size.total).to.be.below(1024 * 1024); // < 1MB
  expect(size.body).to.be.above(0);
});
test("validate response headers", function() {
  const headers = res.getHeaders();
  
  expect(headers).to.have.property('content-type');
  expect(headers['content-type']).to.include('application/json');
  
  expect(res.getHeader('x-rate-limit-remaining')).to.be.a('string');
  
  const rateLimit = parseInt(res.getHeader('x-rate-limit-remaining'));
  expect(rateLimit).to.be.at.least(0);
});
test("validate JSON schema", function() {
  const tv4 = require('tv4');
  const data = res.getBody();
  
  const schema = {
    type: 'object',
    required: ['id', 'name', 'email'],
    properties: {
      id: { type: 'number' },
      name: { type: 'string', minLength: 1 },
      email: { type: 'string', format: 'email' },
      age: { type: 'number', minimum: 0 },
      roles: {
        type: 'array',
        items: { type: 'string' }
      }
    }
  };
  
  const valid = tv4.validate(data, schema);
  expect(valid).to.be.true;
  
  if (!valid) {
    console.error('Schema validation error:', tv4.error);
  }
});
test("validate JWT token", function() {
  const jwt = require('jsonwebtoken');
  const token = res.getBody().token;
  
  // Decode without verification
  const decoded = jwt.decode(token);
  expect(decoded).to.have.property('exp');
  expect(decoded.exp).to.be.greaterThan(Date.now() / 1000);
  
  // Validate claims
  expect(decoded.userId).to.equal(bru.getVar('expectedUserId'));
  expect(decoded.role).to.be.oneOf(['user', 'admin']);
  
  // Store for later use
  bru.setVar('authToken', token);
});

Async Test Validations

// Make additional requests within tests
await test("verify created resource", async () => {
  const createResponse = res.getBody();
  const resourceId = createResponse.id;
  
  // Fetch the created resource
  const getResponse = await bru.sendRequest({
    url: `https://api.example.com/resources/${resourceId}`,
    method: 'GET'
  });
  
  expect(getResponse.status).to.equal(200);
  expect(getResponse.data.id).to.equal(resourceId);
  expect(getResponse.data.name).to.equal(createResponse.name);
});

Error Handling in Tests

// Test expected errors
test("should handle 404 gracefully", function() {
  expect(res.getStatus()).to.equal(404);
  expect(res.getBody()).to.have.property('error');
  expect(res.getBody().error).to.include('not found');
});

// Conditional assertions
test("conditional validation", function() {
  const data = res.getBody();
  
  if (res.getStatus() === 200) {
    expect(data).to.have.property('result');
  } else if (res.getStatus() === 404) {
    expect(data).to.have.property('error');
  } else {
    throw new Error(`Unexpected status: ${res.getStatus()}`);
  }
});

Best Practices

1

Use Descriptive Test Names

// Good
test("should return user profile with all required fields", function() { ... });

// Bad
test("test1", function() { ... });
2

Test One Thing Per Assertion

// Good - focused tests
test("response status is 200", function() {
  expect(res.getStatus()).to.equal(200);
});

test("response contains user data", function() {
  expect(res.getBody()).to.have.property('user');
});

// Avoid - testing too much
test("everything works", function() {
  expect(res.getStatus()).to.equal(200);
  expect(res.getBody()).to.have.property('user');
  expect(res.getBody().user.email).to.be.a('string');
  // ...
});
3

Validate Data Types and Structure

test("validate response structure", function() {
  const data = res.getBody();
  
  // Check types first
  expect(data).to.be.an('object');
  expect(data.items).to.be.an('array');
  
  // Then validate values
  expect(data.success).to.be.true;
  expect(data.items).to.have.lengthOf.at.least(1);
});
4

Use Inline Assertions for Simple Checks

# Simple validations - use inline assertions
assert {
  res.status: eq 200
  res.body.success: eq true
}

# Complex validations - use test scripts
tests {
  test("validate complex data structure", function() {
    // Complex validation logic
  });
}

Common Patterns

Status Code Validation

assert {
  res.status: eq 200
  res.status: in 200,201,204
  res.status: gte 200
  res.status: lt 300
}

Email Validation

assert {
  res.body.email: matches ^[^\s@]+@[^\s@]+\.[^\s@]+$
  res.body.email: contains @
  res.body.email: endsWith @example.com
}

Array Validation

test("validate array", function() {
  expect(res.body.items).to.be.an('array');
  expect(res.body.items).to.not.be.empty;
  expect(res.body.items[0]).to.have.property('id');
});

Performance Checks

assert {
  res.getResponseTime(): lt 2000
  res.getResponseTime(): between 0,5000
}

Scripting

Learn advanced scripting techniques

Variables

Understand variable scopes and usage

Workflows

Build complex request workflows

Testing APIs

Testing fundamentals and best practices

Build docs developers (and LLMs) love