Bruno API Client | Blog & News

Using JavaScript Files in Your Collection | Bruno's Swiss Army Knife

Written by Ryan Reynolds | Apr 17, 2025

Most API clients trap your test logic inside request files. Bruno takes a modern and dev-first approach allowing JavaScript files —reusable utilities that can serve any purpose across your collections.

While Bruno makes JavaScript utilities native to API testing, this pattern is widely used across modern JavaScript development—from test suites to internal tooling to shared libraries across microservices.

In this post, we'll explore:

  • 💡 What makes JavaScript Files so versatile
  • 🛠 Common use cases beyond basic testing
  • 🔍 A practical example using contract testing
  • 🚀 How to structure your utilities

Let's go!

🤔 What Are JavaScript Files in Bruno?

If this is unfamiliar in your regular development flow, think of JavaScript Files as your collection's utility belt. They're standalone .js files that can:

  • Define shared functions / consolidate storage and maintenance to a single file
  • Create test helpers
  • Build validation suites
  • Handle complex transformations

Unlike using the Test tab within a Collection, Folder, or Request, these files:

  • Live at the collection level
  • Can be called within the Test tab of the Collection, Folder, or Request 
  • Can even be stored within an internal npm registry so that anyone across your company can leverage them (great case for contract testing)

 

🔍 Real-World Example: Contract Testing Suite

Let's look at one specific use case: building a contract testing utility using Zod. In a previous post, we built a collection and a set of tests that leverage zod to validate against a schema.

Today we'll use a copy of that collection that is updated with a JavaScript file to centralize the storage of our tests for reusability and cleaner collections.  

You can follow along below, or grab the full collection and script from our public GitHub repo, or just by clicking the Fetch in Bruno button below!
 

Creating a Contract Testing Utility

The first thing we're doing is essentially taking all of the tests we previously had at the individual collection level and putting them into a separate .js file at the root of our collection. You can either do this in a code editor, or if you're on a Pro or Ultimate version of Bruno, click the elipsis next to a collection and select `New Script`.

We now can write our script directly in Bruno: 

 


const { z } = require("zod");

// Base schemas
const BaseAnimalSchema = z.object({
  name: z.string(),
});

// Pet type schema (for the first endpoint)
const PetTypeSchema = BaseAnimalSchema.extend({
  type: z.enum(["dog", "cat", "bird"])
});

// Detailed pet schema (for the vaccine endpoint)
const DetailedPetSchema = BaseAnimalSchema.extend({
  id: z.string().startsWith("pet_"),
  age: z.number().int().nonnegative(),
  vaccinated: z.boolean(),
  tags: z.array(z.string())
});

// Response schemas
const PetTypeResponseSchema = z.object({
  users: z.array(PetTypeSchema)
});

const VaccinatedPetsResponseSchema = z.object({
  pets: z.array(DetailedPetSchema)
});

// Reusable test functions
function validatePetTypeResponse(response) {
  PetTypeResponseSchema.parse(response);
}

function validateVaccinatedPetsResponse(response) {
  VaccinatedPetsResponseSchema.parse(response);
}

function checkAllPetsVaccinated(response) {
  const unvaccinated = response.pets.filter(pet => !pet.vaccinated);
  if (unvaccinated.length > 0) {
    throw new Error(`Found unvaccinated pets: ${unvaccinated.map(p => p.name).join(", ")}`);
  }
}

// Export the functions and schemas
module.exports = {
  validatePetTypeResponse,
  validateVaccinatedPetsResponse,
  checkAllPetsVaccinated,
  // Export schemas in case you need to extend them in specific tests
  BaseAnimalSchema,
  PetTypeSchema,
  DetailedPetSchema
};

Using it in your requests is simple:

Here's an example of how we previously had our test implemented at the request level:

Now, all we have to do in our requests is call the function which contains the relevant script:


const { validateVaccinatedPetsResponse, checkAllPetsVaccinated } = require("./contractTests.js");

test("Valid response structure for pets", () => {
  validateVaccinatedPetsResponse(res.getBody());
});

test("All pets must be vaccinated", () => {
  checkAllPetsVaccinated(res.getBody());
});

Our request-level test is not only much cleaner, but we can now maintain our contract tests in a single location rather than spread throughout the collection.

 

✨ Why This Matters

  1. Separation of Concerns: Keep utilities separate from request logic
  2. Reusability: Write once, use anywhere in your collection
  3. Maintainability: Update shared code in one place
  4. Flexibility: Build any utility you need
  5. Real Node.js: Use any npm package you want

🎯 Best Practices

When building utilities with JavaScript Files:

  1. Single Responsibility: Each file should serve one clear purpose
  2. Clear Naming: Make the utility's purpose obvious (authHelpers.js, contractTests.js)
  3. Documentation: Add JSDoc comments for complex utilities
  4. Modular Design: Export individual functions for flexibility
  5. Version Control: Track your utilities with Git

💭 Final Thoughts

JavaScript Files transform Bruno from a request runner into a complete API testing toolkit. While we've focused on contract testing here, the possibilities are endless. What utilities will you build?

Ready to try it yourself? Create a new JavaScript File in your collection and start building!

Happy testing! 🚀