使用 Jest 在 AWS Lambda 函数中模拟对 S3 的调用

Ric*_*uza 0 mocking amazon-s3 node.js jestjs aws-lambda

我正在尝试使用 Jest 在本地 Node.JS AWS Lambda 函数中模拟对 putObject 的调用,但由于某种原因,我一直在期望中收到 0 次调用。

这是我的主要功能(index.js):

 const S3 = require("aws-sdk/clients/s3");
 const s3 = new S3();

 exports.handler = async (event) => {

   putFunction = async (params, callback) => {
     await s3.putObject(params, callback).promise();
   };

 const params = {
     Bucket: "some value",
     Key: "some key value",
     ContentType: "application/json",
     Body: "some body value",
   };

   const callback = {
     function(err, data) {
       console.log(JSON.stringify(err) + " " + JSON.stringify(data));
     },
   };
   putFunction(params, callback);
 }
Run Code Online (Sandbox Code Playgroud)

我尝试将 async 添加到我的测试函数中,因为我认为这是一个异步问题,但我似乎仍然收到相同的错误。这是我的测试代码(index.test.js):

 let myHandler = require("../../src/lambda/index");
 const mockedPutObject = jest.fn();
 jest.mock("aws-sdk/clients/s3", () => {
   return class S3 {
     putObject(params, cb) {
       mockedPutObject(params, cb);
     }
   };
 });

 it("has to mock s3#putObject", () => {
   const params = {
     Bucket: "test1",
     Key: "test2",
     ContentType: "application/json",
     Body: "test3",
   };

   const callback = {
     function(err, data) {
       console.log(JSON.stringify(err) + " " + JSON.stringify(data));
},
};
   const putFunc = myHandler.handler.putFunction;
   putFunc;
   expect(mockedPutObject).toHaveBeenCalledWith(params, callback);
 });
Run Code Online (Sandbox Code Playgroud)

任何帮助都会很棒。

Jam*_*ris 8

对于那些不想引入任何第三方模拟库(如aws-sdk-mock )的人来说,这是一个 Jest/Node 的唯一答案。

问题(在您的答案中没有看到错误本身)很可能与.promise()您的实现代码中的 相关。

您已经在实现中添加了它,以告诉 SDK 为您调用的任何操作返回一个承诺。

await s3.putObject(params, callback).promise();
Run Code Online (Sandbox Code Playgroud)

返回的 Promise 要么拒绝错误,要么用数据解决。

这意味着在基于Promise 的方法中,您可以完全省略回调

await s3.putObject(params).promise();
Run Code Online (Sandbox Code Playgroud)

(取自这篇 AWS 博客文章

正在修复处理程序...

您可以:

将该回调逻辑放在后续的 Promise 链块中:

.then((data) => {
  // ... do stuff
})
.catch((err) => {
  // ... handle error
}
Run Code Online (Sandbox Code Playgroud)

或者更好(因为看起来你已经在接受)更现代的 ES6 方法

在 try-catch 块中等待 putObject 承诺:

try {
  const data = await s3.putObject(params).promise()
  // ... do things with data on successful response
} catch (err) {
  // ... handle error
}
Run Code Online (Sandbox Code Playgroud)

把这些放在一起

您的处理程序应如下所示:

const { S3 } = require("aws-sdk");
const s3 = new S3();

exports.handler = async (event) => {
  const params = {
    Bucket: "some value",
    Key: "some key value",
    ContentType: "application/json",
    Body: "some body value",
  };

  try {
    const data = await s3.putObject(params).promise();

    // ... do stuff with data

    return {
      statusCode: 200,
      body: JSON.stringify(data),
      // ... etc.
    }
  } catch (err) {
    // ... handle error

    return {
      statusCode: 400, // or any 4XX, 5XX 
      body: '...',     // whatever you wish to return on error
      // ... etc.
    }
  }
}

Run Code Online (Sandbox Code Playgroud)

修复测试...

请记住,您可以省略回调,测试代码需要在处理程序.promise()的调用链中反映额外putObject的内容。

在测试文件中,SDK mock 需要配置为:a) 返回顶级S3构造函数 b) 让这个S3构造函数本身返回一个包含putObject函数的对象c) 让这个putObject本身返回一个包含promise函数的对象

这样它就可以像真正的 SDK 一样被调用:

const { S3 } = require("aws-sdk"); // require("aws-sdk") returns { S3 }

const s3 = new S3()                //                    returns { putObject }

await s3.putObject(params)         //                    returns { promise }
        .promise();                //                    returns ...your_mock_response
Run Code Online (Sandbox Code Playgroud)
// You need to return the { promise } here even if you don't care about
// mock calls beyond the putObject, because the handler itself calls .promise()
// and will throw "TypeError: Cannot read property 'promise' of undefined"

const putObjectMock = jest.fn(() => ({
  promise: jest.fn()
}));

jest.mock('aws-sdk', () => ({
  S3: jest.fn(() => ({
    putObject: putObjectMock,
  })),
}));

// S3 must have jest.fn(...) over an ordinary function otherwise
// the new S3() in the handler will fail.
// Jest does its magic with the function you provide to make it callable as a constructor

const myHandler = require("../../src/lambda/index");

// Don't forget to add the "async" before the "it" callback as your handler is async
it("has to mock s3#putObject", async () => {
  const params = {
    Bucket: "test1",
    Key: "test2",
    ContentType: "application/json",
    Body: "test3",
  };

  await handler(); // Call the handler to then assert against the mock params

  expect(putObjectMock).toHaveBeenCalledWith(params);
});
Run Code Online (Sandbox Code Playgroud)

最后说明 -在模拟设置之后添加处理程序导入以防止"Cannot access 'putObject' before initialization"错误(由处理程序对 SDK 的要求引起)。

希望这可以帮助!