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:
Let's go!
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:
Unlike using the Test
tab within a Collection, Folder, or Request, these files:
Test
tab of the Collection, Folder, or Request
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!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.
When building utilities with JavaScript Files:
authHelpers.js
, contractTests.js
)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! 🚀