DEV Community

Cover image for How to ace API Testing with Requestly
Aniket Bhattacharyea
Aniket Bhattacharyea

Posted on • Originally published at requestly.com

How to ace API Testing with Requestly

Imagine this scenario: You’re debugging an API call. The endpoint only works with production tokens, but your API testing tool insists on syncing everything to the cloud. Suddenly, your production credentials are sitting on someone else’s servers, risking potential exposure.

Most tools default to cloud-first, which can be limiting:

  • Your secrets live in the cloud.
  • Most features require an online account.
  • Tests aren’t versioned with your code.

With Requestly, you get a local-first experience. Your API tests run on your machine, alongside your code, version-controlled just like the rest of your workflow. Your secrets and credentials stay on your device, safe and secure.

Local-first API testing with Requestly

With cloud-first API testing tools, your sensitive information lives in the cloud. One misconfigured account, one permission too broad, and you're risking exposing your sensitive information to the world. Requestly emphasizes local-first API testing that lets developers test APIs without depending on remote setups. You run everything right on your own machine, so you can intercept requests, mock responses, or reproduce tricky edge cases in seconds, all while keeping tokens and sensitive data safe. Because it’s all local, you get fast, reliable feedback that makes debugging smoother and development less painful.

This local-first approach means your tests can be stored next to your code, under Git. By keeping your Requestly assets under Git, you gain the benefits of collaborative development: teammates can review changes through pull requests, track history with commits, and roll back to previous versions if needed. This also fits naturally into existing workflows - your API test rules evolve alongside the code they validate.

In the following few sections of this article, you'll see Requestly in action. As an example of a practical API to test, we'll be using the GitHub REST API.

API Testing with Requestly

To follow along with this tutorial, you'll need the following dependencies on your system:

Setting up Requestly

To make things easy, I have already created a repo that contains the basic setup needed for this article. Clone the repo to your machine.

Install the dependencies:

npm install 
Enter fullscreen mode Exit fullscreen mode

You can either install Requestly as a browser extension or as a desktop app. You can read about the feature differences between these two versions here. For this tutorial, I'll use the desktop app, which you can download from here. So go ahead and install it.

Requestly works on the concept of Workspaces. The demo app already has a local workspace stored, which you need to load. Click on the Workspaces dropdown in the top-left corner and select + Add.

Choose the directory where you just cloned the app, and use requestly-demo as the name of the workspace. This will load the workspace of the same name that is already in the directory.

Once the workspace is ready, navigate to the APIs tab, where you'll find the GitHub API collection. I have already prepared the requests so that we can focus on writing tests in this article. However, if you want to learn more about how to make requests, you can visit the documentation.

List of prepared requests

Note: Apart from REST APIs, Requestly also supports making GraphQL requests

There is also an Environment named "Dev" where you'll need to put your credentials:

  • token: Your PAT
  • owner: Your GitHub username
  • repo: The name of the repo that you chose

We'll start with simple tests, then advance to schema validation and testing workflows. We'll see how to debug and handle errors, and finally, we'll mock a third-party API.

Writing Our First Test

If authentication breaks, your entire API is toast! So, we'll start by testing the Login endpoint. This will show you an example of how to handle authentication. If you click on the collection name and navigate to the Authorization tab, you'll see that the authorization type has been set to Bearer Token. For the token value, we're using {{token}}, which retrieves the value from the environment variable.

Authorization setup

Every endpoint in this collection inherits this authentication method, which means they'll automatically pass the token when you make the request.

Open the Login request and navigate to the Scripts tab. Requestly allows you to run scripts before the request is sent (Pre-request scripts), or after the response is received (Post-response scripts). For the purpose of writing API tests, we'll use the Post-response scripts.

Requestly uses Chai.js for assertions. It exposes the expect method of Chai through rq.expect(), which you can use to write assertions. Requestly also lets you access the request and response objects through rq.request and rq.response respectively, which can be used in the tests.

Our first test will make sure that the server returns 200 Ok response. Let's start by creating a test. We want to check that rq.response.status is equal to OK. So, our test will look like this:

rq.test("Login is successful", () => { rq.expect(rq.response.status).to.equal("OK") }) 
Enter fullscreen mode Exit fullscreen mode

Now, if you click on Send, Requestly will make the request and run the tests. You can see the result of the test in the Tests tab:

The tests tab

Requestly also allows you to check the status code using its name:

rq.response.to.be.ok 
Enter fullscreen mode Exit fullscreen mode

You can find all the status codes in the docs.

Testing Authentication

Now we know that the login request is successful, but how do we know that the login itself works correctly? The login endpoint also returns the logged-in user's data, which we can validate against the ground truth.

Here we can check that the login property matches our username that we put into the owner environment variable:

rq.test("Login returns valid data", () => { var body = JSON.parse(rq.response.body) rq.expect(body).to.have.property("login").that.is.a("string") rq.expect(body.login).to.equal(rq.environment.get("owner")) }) 
Enter fullscreen mode Exit fullscreen mode

Send the requests, and if everything goes correctly, you should see the tests pass.

The tests result tab

Schema Validation

APIs act as contracts between services, and if the structure (fields, types, nesting, required attributes) changes, consumers may break, even if the data looks valid. This is why it's vital to test that the response object has the correct schema.

In the List repos request, we're making a request to the /user/repos endpoint, which fetches all the repos of the logged-in user. Let's assume that we are integrating this API with a frontend that expects the body to be an array of repos, and each repo must have id, name, private, and owner fields. The owner field must have login and id fields, and the name field must match the regex: /^\.?[a-z0-9-_\.]+$/i.

In Requestly, you can use helpers from Chai to do this:

var body = JSON.parse(rq.response.body) rq.test("Fetched data has correct body", () => { // Check that body is an array rq.expect(body).to.be.an("array").that.is.not.empty for (let repo of body) { // Check required fields rq.expect(repo).to.include.keys(["id", "name", "private", "owner"]) rq.expect(repo.owner).to.include.keys(["login", "id"]) // Match regex rq.expect(repo.name).to.match(/^\.?[a-z0-9-_\.]+$/i) } }) 
Enter fullscreen mode Exit fullscreen mode

This test works, but it's very verbose. If you have a more complicated JSON schema, you'll have to spend a lot of time writing out each property one by one. This is also unmaintainable. If the schema changes, changing it in the test will feel like finding a needle in a haystack. You can use .to.have.jsonSchema instead, which lets you pass a JSON schema.

For example, you can write the above test:

rq.test("Match JSON schema", () => { rq.response.to.have.jsonSchema({ type: "array", items: { type: "object", required: ["id", "name", "private", "owner"], properties: { id: { type: "number" }, name: { type: "string", pattern: "^\\.?[a-zA-Z0-9-_\\.]+$" }, private: { type: "boolean" }, owner: { type: "object", required: ["login", "id"], properties: { login: { type: "string" }, id: { type: "number" } } } } } }) }) 
Enter fullscreen mode Exit fullscreen mode

This is much easier to write, understand, and maintain. Of course, let's make sure the tests work.

The tests run successfully

Testing Complete Workflows

Often, your API calls are not as simple as calling one single endpoint. You might need to call one endpoint, process the response, and use the data from the response to call another endpoint. With Requestly, you can use Environment Variables to store arbitrary data and use them in subsequent requests. We have already used one environment variable in the Login endpoint. Let's now take a look at a more complete "workflow" example.

Let's assume that we want to test the pipeline of creating, fetching, and deleting issues. We want to create an issue, fetch the same issue, and then delete it.

In the Create issue endpoint, we're making a request to {{base_url}}/repos/{{owner}}/{{repo}}/issues endpoint. The {{...}} denotes an environment variable that Requestly will substitute. We're creating an issue with the following body:

{ "title": "Test issue from Requestly", "body": "This is a test issue created via API" } 
Enter fullscreen mode Exit fullscreen mode

Let's write a test to ensure the issue is created successfully:

rq.test("Issue was created successfully", () => { rq.response.to.be.created rq.response.to.have.jsonBody("title", "Test issue from Requestly") var body = JSON.parse(rq.response.body) rq.environment.set("issue_id", body.number) }) 
Enter fullscreen mode Exit fullscreen mode

Here, we're checking that the title of the issue matches what we sent in the body. The last line is where we set the issue_id environment variable, which we'll use in our subsequent request.

Open the Fetch issue request. You'll notice we're using the issue_id in the request URL: {{base_url}}/repos/{{owner}}/{{repo}}/issues/{{issue_id}}.

Let's write a test to verify the correct issue is fetched. We'll verify that it has title and body fields, and matches what we created:

rq.test("Issues has correct schema", () => { rq.response.to.have.jsonSchema({ type: "object", required: ["title", "body"], properties: { "title": { type: "string" }, "body": { type: "string" } } }) }) rq.test("Fetches correct issue", () => { rq.response.to.have.jsonBody("title", "Test issue from Requestly") rq.response.to.have.jsonBody("body", "This is a test issue created via API") }) 
Enter fullscreen mode Exit fullscreen mode

Finally, let's close the issue to complete the workflow. In the Close issue request, you'll see we're again using the issue_no variable. This time, let's make sure state is closed in the response:

rq.test("Issue is closed", () => { rq.response.to.have.jsonBody("state", "closed") }) 
Enter fullscreen mode Exit fullscreen mode

You can use this technique of chaining requests in many real-life scenarios. For example, if your API uses username and password to log in and returns a JWT, you can store it in an environment variable and use it in subsequent tests.

Error Handling and Debugging

So far, we've been dealing with tests that work correctly. But in practice, tests may fail. And it's not always easy to debug. Suppose the login test is suddenly failing in CI. Is it because someone pushed buggy code, or is it due to something in the test itself, such as an expired token? Requestly offers debugging features to help you understand why your tests are failing.

When a test fails, Requestly prints an error message next to the test. You can try this out by sending the Invalid token request. This request attempts to log in with an invalid token, but expects the result to be 'ok'.

Test fails

You can also use console.log to log values. You can use rq.request and rq.response objects to access the request and response values to help you debug the actual cause of a failing test. For example, let's log the request headers and response status code:

rq.test("Reuest is successful", () => { console.log(JSON.stringify(rq.request.headers)) console.log(rq.response.status) rq.response.to.be.ok }) 
Enter fullscreen mode Exit fullscreen mode

When you press Ctrl+Alt+I, the Devtools console will open. If you send the request, the logs will show up.

The developer console

These logs can help you debug your test.

Mocking & Intercepting for API Testing

We’ve seen how to handle real errors like bad tokens or missing endpoints. But what about failures that don’t happen on their own? For example, a flaky third-party API or an edge case response you can’t easily trigger in production. That’s where mocking comes in.

Real apps often depend on third-party APIs, such as payment processors, weather data, and analytics. Testing these integrations is tricky because:

  • You can’t control when the third-party API fails.
  • You can’t control the data it sends back.
  • Sometimes, every test call costs real money.

This is where API mocking helps. With Requestly, you can intercept an outgoing request and return a fake response. That way you can simulate failures, edge cases, or alternate data without touching the real API.

Our demo repo has a /httpbin endpoint that calls http://httpbin.org/get. Normally, it just passes through the response. But what if HttpBin goes down? We want to see if our app handles errors gracefully.

To mock an API call, you need to use Requestly's HTTP interceptor, which turns Requestly into a proxy server to intercept HTTP calls from your browsers (Chrome, Firefox, etc.), terminal app, or even the whole system. Then you can use HTTP rules to modify the request, response, headers, introduce delays, redirect, and much more.

As of writing this article, this feature is not available in a local workspace. So, you'll need either a team workspace or switch to the private workspace. In any case, you'll need an account. Click on the icon in the top-right corner, and sign up or sign in.

Now in the workspace dropdown menu, you'll see that the Private workspace option is enabled. Choose this option to switch to the private workspace.

Go to the Network tab, click on Connect apps, switch to the Terminal processes tab, click on Setup instructions, and copy the command.

The terminal proxy setup instructions

Paste the command in the terminal and run it. On successful execution, you should see the message Requestly interception enabled. Now start the server with npm start. Now, any request made from the API server will pass through Requestly.

Note: Running the command provided by Requestly sets up a few environment variables, one of them being https_proxy, which sets up Requestly as a proxy server. The demo app uses Axios to make requests to HttpBin. Axios automatically picks up the https_proxy environment variable, so in this case, we don't have to change any code. However, suppose you use a different library (such as node-fetch) that doesn't automatically pick up the https_proxy environment variable. In that case, you'll need to modify the code to use the proxy server manually.

In another terminal, invoke the /httpbin endpoint:

curl localhost:3000/httpbin 
Enter fullscreen mode Exit fullscreen mode

In Requestly, you should see the HttpBin request show up:

The HttpBin request

Note: As of writing the article, requests made with Axios show up with the wrong URL due to a bug.

Right-click on it and select Modify response body.

This will open a window where you can define an HTTP rule that will modify the response body of the intercepted request.

First, give the rule a name, for example: "HttpBin error". In the If request field, select URL and Contains and write httpbin.org in the input field. Select 500 from the status codes dropdown, enter an empty body, and check Serve this response body without making a call to the server. Then, save the rule.

Modify response body details

Now, anytime our API server makes a request to HttpBin, Requestly will return a 500 response without actually hitting HttpBin.

Let's now write the test. Go to APIs tab, and create a new HTTP request. Provide a name such as Get third party error, and put http://localhost:3000 in the URL field, and write the test:

rq.test("Should return error", () => { rq.response.to.have.status(500) }) 
Enter fullscreen mode Exit fullscreen mode

Now I can test whether my app correctly handles failures or not without having to wait for the third-party API to fail!

With these tests in place - from happy paths to deliberate failures - we've covered the core scenarios of real-world API testing. Now it's your turn to use Requestly for testing your APIs.

Conclusion

Effective API testing goes beyond checking if an endpoint returns data - it ensures reliability, catches edge cases, and streamlines development. By going local-first with Requestly, you can quickly test and mock APIs without juggling cloud accounts or heavy setups. Start small with a few tests, then layer on more complexity as your needs grow. The best way to see the value is to try it in your own workflow and feel how much smoother debugging and iteration can become.

Top comments (0)