Home > Software design >  Why does jest time out when running a test on mock AWS file read?
Why does jest time out when running a test on mock AWS file read?

Time:11-19

I am trying to write a unit test (jest) to test the execution of a service. Currently, the test times out and I am positive it has to do with the point at with I create the read stream. Am I mocking s3.getObject.createReadStream() correctly?

handler.ts

const XLSX = require("xlsx");
const AWS = require("aws-sdk");
import { Pool } from "pg";

class Service {
    client: any;
    workbook: any = "";

    constructor(client) {
        this.workbook = "";
        this.client = client;
    }

    async createWorkbookFromS3(filePath: string, id: string) {
    
        const fileLocation = id   filePath;
    
        const params = {
          Bucket: "bucket",
          Key: fileLocation,
        };
    
        function getBufferFromS3(callback) {
          const buffers = [];
          const s3 = new AWS.S3();
          const stream = s3.getObject(params).createReadStream();
          stream.on("data", (data) => buffers.push(data));
          stream.on("end", () => callback(null, Buffer.concat(buffers)));
          stream.on("error", (error) => callback(error));
        }
    
        function getBufferFromS3Promise() {
          return new Promise((resolve, reject) => {
            getBufferFromS3((error, s3buffer) => {
              if (error) {
                return reject(error)
              };
              return resolve(s3buffer);
            });
          });
        }
        try {
          const buffer = await getBufferFromS3Promise();
          this.workbook = XLSX.read(buffer);
        } catch (e) {
          if (e) {
            this.client.release()
            console.error(e);
            throw new Error("Unable to read the xlsx from S3");
          }
        }
      }
}


const decoder = async (event: any) => {
    const id: string = event.body["id"];
    const filePath = `${id}.xlsx`;
  
    try {
      switch (event.path) {
        case "/vsiDecoder/draft":
          try {
            const filePath: string = event.body["filePath"];
  
            const dbCredentials = {"user":"postgres","password":"password","host":"awsconnectihoststring","port": "0000" ,"database":"db"}
            const pool = new Pool(JSON.parse(dbCredentials['SecretString']));
            const client = await pool.connect();
            const vsiObj = new Service(client);
  

            await vsiObj.createWorkbookFromS3(filePath, id);
  
            vsiObj.client.release()
            pool.end();
  
          } catch (e) {
            throw new Error(e)
          }
  
          return {
            statusCode: 200,
            message: "The document has been successfully drafted.",
          };

        default:
          return {
            message: "Bad request",
            statusCode: 400,
          };
      }
    } catch (e) {
      console.log(e)
      return{
        statusCode: 400,
        message: e.message,
      }
    }
  };

  module.exports = {Service, decoder }

handler.test.ts

const decoder = require('./handler.ts')
import { Pool } from 'pg';

const mockgetObject = {
    createReadStream: jest.fn(() => {
        return {
            on: jest.fn()
        }
    })
}
const mockS3Instance = {
    putObject: jest.fn().mockReturnThis(),
    promise: jest.fn().mockReturnThis(),
    catch: jest.fn(),
    getObject: jest.fn(() => mockgetObject)
}
jest.mock('aws-sdk', () => {
    return {
        S3: jest.fn(() => mockS3Instance),
    }
});

jest.mock('pg', () => {
    const mClient = {
        connect: jest.fn().mockReturnThis(),
        query: jest.fn().mockReturnThis(),
        end: jest.fn().mockReturnThis(),
        release: jest.fn().mockReturnThis(),
    };
    return { Pool: jest.fn(() => mClient) };
});

describe('Service Test', () => {
    let client;
    beforeEach(() => {
        client = new Pool();
        jest.setTimeout(30000);
    });
    afterEach(() => {
        jest.clearAllMocks();
    });

    it('should executre decoder', async () => {
        const id = 'id';
        const filePath = 'filePath'
        const service = new decoder.Service(client);
        await service.createWorkbookFromS3(id, filePath)
        const events = { 'body': { 'id': '1234', "path": "/decoder/draft", "vsiFilePath": "path" } };
        const response = '{"statusCode":200,"message":"The document has been succesfully drafted."}';
        expect((await decoder.decoder(events)).body).toEqual(response);
    })
})

error:

 Service Test › should executre decoder

    : Timeout - Async callback was not invoked within the 30000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 30000 ms timeout specified by jest.setTimeout.Error:

Thanks to any help! I do believe that I am successfully mocking createReadStream but if there is a way to mock getBufferFromS3Promise that would be acceptable instead.

CodePudding user response:

In your createReadStream mock, the on function has no implementation. However, in the code for getBufferFromS3(callback), the on function is responsible for setting up the event handlers that execute the callback function, which is where the Promise from getBufferFromS3Promise will be resolved or rejected. Because that promise is never resolved or rejected, your asynchronous test code never finishes.

You need to implement a mock of the on function that will execute the event handler callbacks somehow. If you are only testing the success case you could just execute the handler synchronously within on since the 'end' event happens to be hooked up before the 'error' event. A better approach might be to mock the event emitter by having on save the handlers, and adding a triggerEvent function to the stream mock that allows you to imperatively trigger the different stream events within your test.

  • Related