4

I am writing a mock test case with jest in typescript & trying to mock API calls with supertest, but unable to get a mocked response in return, as I'm using Axios on the login function & even tried to mock the Axios call but got no luck.

Here's my code :

auth.controller.ts

import { AxiosService } from "helpers";

export class AuthController {
    constructor() {
       ...
      // Some logic is written here
      this.init()
    }

    public init() {
       // prepared an route for login
       router.post('/api/auth/login', this.login);
    }
  
    login = async function (req: Request, res: Response): Promise<void> {
        // Preparing config for axios call with some logic
        ...

        // created a service for Axios call to some third party.
        AxiosService.call(req, res, config, "login");
    }
  }
    
    

auth.test.ts

import { AuthController } from "../../modules/auth/auth.controller";
jest.mock("../../modules/auth/auth.controller");

beforeAll(async () => {
  const auth = new AuthController();
  mockLogin = jest.spyOn(auth, "login");
});

afterAll(async () => {
  server.stop();
});

test("should give login response", async () => {
    mockLogin.mockImplementation(() => {
      return Promise.resolve({ Success: true, body: "Login" });
    });

      const response = await request(server.app)
        .post("/api/auth/login")
        .send(requestBody)
        .expect(200);

      response.body // Getting the server response instead of mocked one
})
    
    

Tried with this code too, but no luck over there :

jest.mock('../../modules/auth/auth.controller', () => {
  return { 
    AuthController: jest.fn().mockImplementation(() => {
          return {
              login: jest.fn()
          }   
      })
  }
})
            
    

Here's my AxiosService class looks like :

export class AxiosService {

  public static async call(...):Promise<void> { 
    try { 
       const { data } = await axios(...);

       res.status(200).json(data);
    } catch(err) {
       res.status(400).send(err);
     }
  
  }

}

Tried to mock AxiosService call method with the following line :

jest.mock('../../helpers/AxiosService', () => {
  return jest.fn().mockImplementation(() => {
    return { call: () => { return {success:true, data:'mock'}} }
  })
})

However, after mocking the Axios call, I'm getting Async callback was not invoked within the 10000 (which I've given) ms timeout specified by jest.setTimeout

Anyone can help that would be great for me, as I'm new to the mocking concept so maybe I'm missing something here.

Thanks in advance

Satish Joshi
  • 71
  • 1
  • 7
  • It doesn't make sense to mock AuthController because it's the unit that you test. Mock other units like AxiosService. – Estus Flask Sep 28 '20 at 09:05
  • @EstusFlask, Thank you for replying, I've tried it but it's not working as expected. – Satish Joshi Sep 28 '20 at 10:15
  • What did not work? Any way, this is how it needs to be done, the question should be about that attempt and not mocking AuthController for its own test. – Estus Flask Sep 28 '20 at 10:18
  • @EstusFlask, I mocked the call function of AxiosService, through jest.mock(...), but instead of giving any response it fails to complete the operation by giving “Async callback was not invoked within the 10000 (which I've given) ms timeout specified by jest.setTimeout”. Actually, I'm a bit new to this concept as well maybe I'm missing something here. – Satish Joshi Sep 28 '20 at 11:35
  • Please, provide https://stackoverflow.com/help/mcve that can reproduce the problem. It's unknown what was wrong with this. – Estus Flask Sep 28 '20 at 11:37
  • @EstusFlask, I've mentioned the following code on the above question raised, you can check & let me know in case anything required. – Satish Joshi Sep 28 '20 at 14:51
  • 1
    Notice that AuthController and AxiosService are tightly coupled and may be awkward to test. The separation between model (service) and controller allows to test them separately, while your service is the continuation of the controller, and the controller doesn't control anything. Generally a controller should control the response a controller, `res.json(await service.getResult())`. while a service takes care of business logic. – Estus Flask Sep 28 '20 at 15:15
  • @EstusFlask, I need to ask one more thing, do I need to mock the middleware part as well, as I'm using express-validator in my routes to validate the request body. – Satish Joshi Sep 29 '20 at 11:50
  • Depends on the case. That part needs to be tested any way, mocked or not. If validation and the rest of the controller aren't separated (e.g. you have designated `validate` method that uses express-validator) cannot be easily tested then it probably should be tested together. Third-party libs are commonly mocked in unit tests to reduce the amount moving parts and prevent undesirable side effects, but a validator doesn't cause side effects so it's probably fine. – Estus Flask Sep 29 '20 at 13:28

1 Answers1

3

A proper testing strategy that every unit but tested one needs to be mocked.

A timeout happens because Supertest waits for server response and there's none in case of mocked AxiosService, so it needs be mocked like:

...
return { call: (req, res) => { res.status(200).json({success:true, data:'mock'}); }

In this case the test can either mock controller class with mocked service class, or test them together with mocked Axios. Since AuthController doesn't do much alone (AuthController and AxiosService are abstractions over simple Express handler), it can be:

jest.mock('axios', () => jest.fn()) // at top level
...
axios.mockResolvedValue({ data: ... });
const response = await request(server.app)
Estus Flask
  • 206,104
  • 70
  • 425
  • 565