Should I mock http request to an external API in functional test over a REST API?


#1

Hi everyone! I’m building a simple REST API and I’m learning how to do testing in AdonisJS.
Basically I have an endpoint that calls an external api through a js library and in order to test this endpoint I’m writing a functional test like this:

const { test, trait } = use("Test/Suite")("Login");

trait("Test/ApiClient");
trait("DatabaseTransactions");

test("Can get posters if query is given", async ({ client, assert }) => {
  const response = await client.post("/posters?query=2130091").end();

  response.assertStatus(200);
  assert.isString(response.body.data[0].title);
  assert.isNumber(response.body.data[0].year);
  assert.isUrl(response.body.data[0].posterURL);
});

Now in the controller for the /posters route should I mock the request when testing? If so, how do I do?

Generally, online, I saw that requests are mocked in unit testing, but being this a functional test, I believe that verifying that the external API works should be part of the test

PS. of course I’m new to testing, so sorry if it’s a silly question


#2

Yeah, this is general question everyone have when writing tests. No matter whether it’s unit tests or functional tests, you should never mock what you don’t own.

However, things are not that simple.

  • At times the third party API’s are slow.
  • Or they have rate limiting
  • Or maybe they will effect the production data.

In these cases, you can try to make your life easier by properly structuring your code. I never use any mocking libraries, coz they are kind of black magic for me.

What I will do in this situation is.

  1. Create a service and wrap all external calls in this Service. For example: If I am calling the Github API, then I will create a service called App/Services/Github.js
  2. I will inject this in my controller constructor.
  3. During testing, I will provide a different copy of this service to my controller and hence calls to Github are eliminated.

app/Services/Github.js

class Github {
  async fetchRepos () {
  }
}

module.exports = Github

app/Controllers/RepoController.js

class RepoController {
   constructor (gh) {
       this.gh = gh
   }

   static get inject () {
       return ['App/Services/Github']
   }
}

During testing

ioc.fake('App/Services/Github', () => {
   class FakeGh {
       fetchRepos () {}
   }

   new new FakeGh()
})

#3

Yeah I’ve already created a service (I’m shaping the app right now so I’m constantly changing something).

This doesn’t seem to me so much different than a mock! The mock is the FakeGh.fetchRepos implementation… Is a mock something different that I don’t know?

Following Stack Overflow:

Mock is a method/object that simulates the behavior of a real method/object in controlled ways. Mock objects are used in unit testing . Often a method under a test calls other external services or methods within it. These are called dependencies.

Anyway this is basically the same thing we do in Angular through its DI.
The thing I don’t get is this:

   static get inject () {
       return ['App/Services/Github']
   }

I don’t get what that static get inject is for! If I want to use a registered service I just do use('App/Services/Github'), what am I missing?


#4

Ok, I’ve just went through the docs and noticed that that “static get inject” comes from adonis 3.2 and it is a way to inject a service inside a controller.
The point is that, as I said, I just use “use” keyword to require a service or a model as you do in this updated repo: https://github.com/adonisjs/adonis-blog-demo/blob/master/app/Controllers/Http/PostController.js

So why there are two ways of doing this and when I should chose one upon the other?