Mocking a connection call to AWS DynamoDB in sinon.

huangapple go评论75阅读模式
英文:

Mocking a connection call to AWS DynamoDB in sinon

问题

Handler.js 中的 saveUser 方法似乎在测试中仍然尝试连接到 DynamoDB。这可能是因为 db.put 函数返回了一个 Promise 以及在回调中使用了它。您可以尝试将 db.put 函数的 stub 更改为以下形式,以确保它返回一个正确的 Promise:

sandbox.stub(db, "put").callsFake((params, callback) => {
  callback(null, { statusCode: 200 });
});

这将模拟 DynamoDB 的 put 方法并返回一个成功的响应,而不会尝试实际连接到 DynamoDB。这应该解决您的测试问题。

英文:

I have an endpoint in API Gateway which is mapped to a Lambda function in AWS. While writing test cases for the new handler function of the endpoint, I do not want the spec file to call the actual API or connect to DynamoDB. I tried to add a sinon.stub, but it still made the call to connect to DynamoDB and the test-case failed. I am unable to locate where the stub went wrong.

Handler.js:

<!-- begin snippet: js hide: false console: false babel: false -->

<!-- language: lang-js -->

saveUser(userName, logger) {
  const Item = {
    id: uuid.v4(),
    userName,
    ttl: parseInt(Date.now() / 1000) + 900 // expire the name after 15 minutes from now
  };
  const params = {
    TableName: &quot;my-table-name&quot;,
    Item
  };
  logger.log(`Saving new user name to DynamoDB: ${JSON.stringify(params)}`);

  return new Promise(function(resolve, reject) {
    db.put(params, function(err, _) {
      if (err) {
        logger.exception(`Unable to connect to DynamoDB to create: ${err}`);
        reject({
          statusCode: 404,
          err
        });
      } else {
        logger.log(`Saved data to DynamoDB: ${JSON.stringify(Item)}`);
        resolve({
          statusCode: 201,
          body: Item
        });
      }
    });
  });
}

<!-- end snippet -->

Handler.spec.js:

<!-- begin snippet: js hide: false console: false babel: false -->

<!-- language: lang-js -->

import AWS from &quot;aws-sdk&quot;;
const db = new AWS.DynamoDB.DocumentClient({
  apiVersion: &quot;2012-08-10&quot;
});

describe(&quot;user-name-handler&quot;, function() {
  const sandbox = sinon.createSandbox();
  afterEach(() =&gt; sandbox.restore());

  it(&quot;Test saveUser() method&quot;, async function(done) {
    const {
      saveUser
    } = userHandler;

    sandbox.stub(db, &quot;put&quot;)
      .returns(new Promise((resolve, _) =&gt; resolve({
        statusCode: 200
      })));

    try {
      const result = await saveUser(&quot;Sample User&quot;, {
        log: () =&gt; {},
        exception: () =&gt; {}
      });

      expect(result).to.be.equal({
        data: &quot;some data&quot;
      });
      done();
    } catch (err) {
      console.log(err);
      done();
    }
  });
});

<!-- end snippet -->

Error:

Error: Resolution method is overspecified. Specify a callback *or* return a Promise; not both.

I console-logged the err object and it gave me this error, which made me think it is trying to connect to the DynamoDB.

Error: connect ENETUNREACH 127.0.0.1:80
  at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1144:16) {
message: &#39;Missing credentials in config, if using AWS_CONFIG_FILE, set AWS_SDK_LOAD_CONFIG=1&#39;,       
errno: &#39;ENETUNREACH&#39;,
code: &#39;CredentialsError&#39;,
syscall: &#39;connect&#39;,
address: &#39;127.0.0.1&#39;,
port: 80,
time: 2023-05-07T10:45:25.835Z,
originalError: {
  message: &#39;Could not load credentials from any providers&#39;,
  errno: &#39;ENETUNREACH&#39;,
  code: &#39;CredentialsError&#39;,
  syscall: &#39;connect&#39;,
  address: &#39;127.0.0.1&#39;,
  port: 80,
  time: 2023-05-07T10:45:25.835Z,
  originalError: [Object]
}

Related: https://stackoverflow.com/questions/76192814/how-to-test-a-method-which-returns-data-from-aws-dynamodb

答案1

得分: 1

你正在嘲笑测试文件中声明的db,而不是saveUser实际使用的db

解决方案是将db声明移到它自己的模块中,例如:db.js

const AWS = require("aws-sdk");

const db = new AWS.DynamoDB.DocumentClient({
  apiVersion: "2012-08-10"
});

module.exports = db;

然后从saveUser模块和测试模块都导入它,这样我们将模拟与saveUser使用的相同db实例。

更新:我成功运行了以下代码的测试:

测试代码:

const sinon = require('sinon');
const { saveUser } = require('../userHandler');
const { expect } = require('chai');

const db = require('../db');

describe('user-name-handler', function() {
  afterEach(() => sinon.restore());

  it('Test saveUser() method', async function() {

      sinon.stub(db, 'put')
        .returns(new Promise((resolve, _) => resolve({
            statusCode: 201,
            body: 'some data'
      })));

      try {
          const result = await saveUser(db, 'Sample User', {
            log: () => {},
            exception: () => {}
          });

          expect(result).to.deep.equal({
            statusCode: 201,
            body: 'some data'
          });
      } catch (err) {
        console.log('err', err);
      }
  });
});

userHandler 文件:

const db = require('./db');

const saveUser = (db, userName, logger) => {
  const Item = {
    id: uuid.v4(),
    userName,
    ttl: parseInt(Date.now() / 1000) + 900 // 在当前时间的15分钟后过期名称
  };
  const params = {
    TableName: "my-table-name"
  };
  logger.log(`Saving new user name to DynamoDB: ${JSON.stringify(params)}`);

    return db.put(params, function(err, _) {
      if (err) {
        logger.exception(`Unable to connect to DynamoDB to create: ${err}`);
        return reject({
          statusCode: 404,
          err
        });
      } else {
        logger.log(`Saved data to DynamoDB: ${JSON.stringify(Item)}`);
        return resolve({
          statusCode: 201,
          body: Item
        });
      }
    });
}

module.exports = { saveUser };

这些代码示例中的注释和错误处理需要进一步处理。希望这能帮助你。

英文:

You are mocking the db that is declared in the test file - not the db that saveUser is actually using.

The solution is to move the db declaration to its own module, say: db.js

const AWS = require(&quot;aws-sdk&quot;);

const db = new AWS.DynamoDB.DocumentClient({
  apiVersion: &quot;2012-08-10&quot;
});

module.exports = db;

and then import it both from the module of saveUser and from the test - so that we'll be mocking the same db instance that saveUser uses.

UPDATE

I was able to run the test with the following code successfully:

The test code:

const sinon = require(&#39;sinon&#39;);
const { saveUser } = require(&#39;../userHandler&#39;);
const { expect } = require(&#39;chai&#39;);

const db = require(&#39;../db&#39;);

describe(&#39;user-name-handler&#39;, function() {
  afterEach(() =&gt; sinon.restore());

  it(&#39;Test saveUser() method&#39;, async function() {

      sinon.stub(db, &#39;put&#39;)
        .returns(new Promise((resolve, _) =&gt; resolve({
            statusCode: 201,
            body: &#39;some data&#39;
      })));

      try {
          const result = await saveUser(db, &#39;Sample User&#39;, {
            log: () =&gt; {},
            exception: () =&gt; {}
          });

          expect(result).to.deep.equal({
            statusCode: 201,
            body: &#39;some data&#39;
          });
      } catch (err) {
        console.log(&#39;err&#39;, err);
      }
  });
});

userHandler file:

const db = require(&#39;./db&#39;);

const saveUser = (db, userName, logger) =&gt; {
  const Item = {
    id: uuid.v4(),
    userName,
    ttl: parseInt(Date.now() / 1000) + 900 // expire the name after 15 minutes from now
  };
  const params = {
    TableName: &quot;my-table-name&quot;
  };
  logger.log(`Saving new user name to DynamoDB: ${JSON.stringify(params)}`);

    return db.put(params, function(err, _) {
      if (err) {
        logger.exception(`Unable to connect to DynamoDB to create: ${err}`);
        return reject({
          statusCode: 404,
          err
        });
      } else {
        logger.log(`Saved data to DynamoDB: ${JSON.stringify(Item)}`);
        return resolve({
          statusCode: 201,
          body: Item
        });
      }
    });
}

module.exports = { saveUser };

package.json

{
  &quot;name&quot;: &quot;play&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;&quot;,
  &quot;main&quot;: &quot;index.js&quot;,
  &quot;scripts&quot;: {
    &quot;test&quot;: &quot;mocha --timeout 5000&quot;
  },
  &quot;author&quot;: &quot;&quot;,
  &quot;license&quot;: &quot;ISC&quot;,
  &quot;dependencies&quot;: {
    &quot;aws-sdk&quot;: &quot;^2.1373.0&quot;,
    &quot;chai&quot;: &quot;^4.3.7&quot;,
    &quot;mocha&quot;: &quot;^10.2.0&quot;,
    &quot;sinon&quot;: &quot;^15.0.4&quot;
  }
}

OUTPUT
Mocking a connection call to AWS DynamoDB in sinon.

答案2

得分: 0

# 在一个文件中分离DB连接
我们可以将DB连接分离到一个不同的文件中并在处理程序实现以及*spec*文件中导入它

**db.js**
```js
import AWS from &quot;aws-sdk&quot;;
const db = new AWS.DynamoDB.DocumentClient({ apiVersion: &quot;2012-08-10&quot; });

export default db;

yields() 函数

在存根直接返回Promise的地方,应该与它的回调一起链接一个.yields()。我们可以更改参数以覆盖代码的各个分支。

代码

describe(&quot;user-handler connection success&quot;, function () {
    const sandbox = sinon.createSandbox();
    afterEach(() =&gt; sandbox.restore());
    before(() =&gt; {
        sinon.stub(db, &quot;put&quot;)
            .yields(null, true);
        sinon.stub(db, &quot;get&quot;)
            .yields(null, { sampleKey: &quot;sample value&quot; });
        sinon.stub(db, &quot;delete&quot;)
            .yields(null, { sampleKey: &quot;sample value&quot; });
    });
    after(() =&gt; {
        db.put.restore();
        db.get.restore();
        db.delete.restore();
    });

    it(&quot;Test saveUser() method success&quot;, async function () {
        const result = await userHandler.saveToken(&quot;sample user&quot;, {
            log: () =&gt; {},
            exception: () =&gt; {}
        });

        expect(result.statusCode).to.be.equal(201);
    });
});

有用链接

https://www.youtube.com/watch?v=vXDbmrh0xDQ


<details>
<summary>英文:</summary>

# Separating the DB connection in a file
We can separate the DB connection in a different file and import it in the handler implementation as well as in the *spec* file.

**db.js**
```js
import AWS from &quot;aws-sdk&quot;;
const db = new AWS.DynamoDB.DocumentClient({ apiVersion: &quot;2012-08-10&quot; });

export default db;

The yields() function

Instead of the stub directly return a Promise, it should be chained with a .yields() along with the parameters its callback will accept. We can change the parameters to cover various branches of the code.

Code

describe(&quot;user-handler connection success&quot;, function () {
    const sandbox = sinon.createSandbox();
    afterEach(() =&gt; sandbox.restore());
    before(() =&gt; {
        sinon.stub(db, &quot;put&quot;)
            .yields(null, true);
        sinon.stub(db, &quot;get&quot;)
            .yields(null, { sampleKey: &quot;sample value&quot; });
        sinon.stub(db, &quot;delete&quot;)
            .yields(null, { sampleKey: &quot;sample value&quot; });
    });
    after(() =&gt; {
        db.put.restore();
        db.get.restore();
        db.delete.restore();
    });

    it(&quot;Test saveUser() method success&quot;, async function () {
        const result = await userHandler.saveToken(&quot;sample user&quot;, {
            log: () =&gt; {},
            exception: () =&gt; {}
        });

        expect(result.statusCode).to.be.equal(201);
    });
});

Useful Link

https://www.youtube.com/watch?v=vXDbmrh0xDQ

huangapple
  • 本文由 发表于 2023年5月7日 20:23:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/76193901.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定