Mocking a connection call to AWS DynamoDB in sinon.

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

Mocking a connection call to AWS DynamoDB in sinon

问题

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

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

这将模拟 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 -->

  1. saveUser(userName, logger) {
  2. const Item = {
  3. id: uuid.v4(),
  4. userName,
  5. ttl: parseInt(Date.now() / 1000) + 900 // expire the name after 15 minutes from now
  6. };
  7. const params = {
  8. TableName: &quot;my-table-name&quot;,
  9. Item
  10. };
  11. logger.log(`Saving new user name to DynamoDB: ${JSON.stringify(params)}`);
  12. return new Promise(function(resolve, reject) {
  13. db.put(params, function(err, _) {
  14. if (err) {
  15. logger.exception(`Unable to connect to DynamoDB to create: ${err}`);
  16. reject({
  17. statusCode: 404,
  18. err
  19. });
  20. } else {
  21. logger.log(`Saved data to DynamoDB: ${JSON.stringify(Item)}`);
  22. resolve({
  23. statusCode: 201,
  24. body: Item
  25. });
  26. }
  27. });
  28. });
  29. }

<!-- end snippet -->

Handler.spec.js:

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

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

  1. import AWS from &quot;aws-sdk&quot;;
  2. const db = new AWS.DynamoDB.DocumentClient({
  3. apiVersion: &quot;2012-08-10&quot;
  4. });
  5. describe(&quot;user-name-handler&quot;, function() {
  6. const sandbox = sinon.createSandbox();
  7. afterEach(() =&gt; sandbox.restore());
  8. it(&quot;Test saveUser() method&quot;, async function(done) {
  9. const {
  10. saveUser
  11. } = userHandler;
  12. sandbox.stub(db, &quot;put&quot;)
  13. .returns(new Promise((resolve, _) =&gt; resolve({
  14. statusCode: 200
  15. })));
  16. try {
  17. const result = await saveUser(&quot;Sample User&quot;, {
  18. log: () =&gt; {},
  19. exception: () =&gt; {}
  20. });
  21. expect(result).to.be.equal({
  22. data: &quot;some data&quot;
  23. });
  24. done();
  25. } catch (err) {
  26. console.log(err);
  27. done();
  28. }
  29. });
  30. });

<!-- end snippet -->

Error:

  1. 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.

  1. Error: connect ENETUNREACH 127.0.0.1:80
  2. at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1144:16) {
  3. message: &#39;Missing credentials in config, if using AWS_CONFIG_FILE, set AWS_SDK_LOAD_CONFIG=1&#39;,
  4. errno: &#39;ENETUNREACH&#39;,
  5. code: &#39;CredentialsError&#39;,
  6. syscall: &#39;connect&#39;,
  7. address: &#39;127.0.0.1&#39;,
  8. port: 80,
  9. time: 2023-05-07T10:45:25.835Z,
  10. originalError: {
  11. message: &#39;Could not load credentials from any providers&#39;,
  12. errno: &#39;ENETUNREACH&#39;,
  13. code: &#39;CredentialsError&#39;,
  14. syscall: &#39;connect&#39;,
  15. address: &#39;127.0.0.1&#39;,
  16. port: 80,
  17. time: 2023-05-07T10:45:25.835Z,
  18. originalError: [Object]
  19. }

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

  1. const AWS = require("aws-sdk");
  2. const db = new AWS.DynamoDB.DocumentClient({
  3. apiVersion: "2012-08-10"
  4. });
  5. module.exports = db;

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

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

测试代码:

  1. const sinon = require('sinon');
  2. const { saveUser } = require('../userHandler');
  3. const { expect } = require('chai');
  4. const db = require('../db');
  5. describe('user-name-handler', function() {
  6. afterEach(() => sinon.restore());
  7. it('Test saveUser() method', async function() {
  8. sinon.stub(db, 'put')
  9. .returns(new Promise((resolve, _) => resolve({
  10. statusCode: 201,
  11. body: 'some data'
  12. })));
  13. try {
  14. const result = await saveUser(db, 'Sample User', {
  15. log: () => {},
  16. exception: () => {}
  17. });
  18. expect(result).to.deep.equal({
  19. statusCode: 201,
  20. body: 'some data'
  21. });
  22. } catch (err) {
  23. console.log('err', err);
  24. }
  25. });
  26. });

userHandler 文件:

  1. const db = require('./db');
  2. const saveUser = (db, userName, logger) => {
  3. const Item = {
  4. id: uuid.v4(),
  5. userName,
  6. ttl: parseInt(Date.now() / 1000) + 900 // 在当前时间的15分钟后过期名称
  7. };
  8. const params = {
  9. TableName: "my-table-name"
  10. };
  11. logger.log(`Saving new user name to DynamoDB: ${JSON.stringify(params)}`);
  12. return db.put(params, function(err, _) {
  13. if (err) {
  14. logger.exception(`Unable to connect to DynamoDB to create: ${err}`);
  15. return reject({
  16. statusCode: 404,
  17. err
  18. });
  19. } else {
  20. logger.log(`Saved data to DynamoDB: ${JSON.stringify(Item)}`);
  21. return resolve({
  22. statusCode: 201,
  23. body: Item
  24. });
  25. }
  26. });
  27. }
  28. 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

  1. const AWS = require(&quot;aws-sdk&quot;);
  2. const db = new AWS.DynamoDB.DocumentClient({
  3. apiVersion: &quot;2012-08-10&quot;
  4. });
  5. 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:

  1. const sinon = require(&#39;sinon&#39;);
  2. const { saveUser } = require(&#39;../userHandler&#39;);
  3. const { expect } = require(&#39;chai&#39;);
  4. const db = require(&#39;../db&#39;);
  5. describe(&#39;user-name-handler&#39;, function() {
  6. afterEach(() =&gt; sinon.restore());
  7. it(&#39;Test saveUser() method&#39;, async function() {
  8. sinon.stub(db, &#39;put&#39;)
  9. .returns(new Promise((resolve, _) =&gt; resolve({
  10. statusCode: 201,
  11. body: &#39;some data&#39;
  12. })));
  13. try {
  14. const result = await saveUser(db, &#39;Sample User&#39;, {
  15. log: () =&gt; {},
  16. exception: () =&gt; {}
  17. });
  18. expect(result).to.deep.equal({
  19. statusCode: 201,
  20. body: &#39;some data&#39;
  21. });
  22. } catch (err) {
  23. console.log(&#39;err&#39;, err);
  24. }
  25. });
  26. });

userHandler file:

  1. const db = require(&#39;./db&#39;);
  2. const saveUser = (db, userName, logger) =&gt; {
  3. const Item = {
  4. id: uuid.v4(),
  5. userName,
  6. ttl: parseInt(Date.now() / 1000) + 900 // expire the name after 15 minutes from now
  7. };
  8. const params = {
  9. TableName: &quot;my-table-name&quot;
  10. };
  11. logger.log(`Saving new user name to DynamoDB: ${JSON.stringify(params)}`);
  12. return db.put(params, function(err, _) {
  13. if (err) {
  14. logger.exception(`Unable to connect to DynamoDB to create: ${err}`);
  15. return reject({
  16. statusCode: 404,
  17. err
  18. });
  19. } else {
  20. logger.log(`Saved data to DynamoDB: ${JSON.stringify(Item)}`);
  21. return resolve({
  22. statusCode: 201,
  23. body: Item
  24. });
  25. }
  26. });
  27. }
  28. module.exports = { saveUser };

package.json

  1. {
  2. &quot;name&quot;: &quot;play&quot;,
  3. &quot;version&quot;: &quot;1.0.0&quot;,
  4. &quot;description&quot;: &quot;&quot;,
  5. &quot;main&quot;: &quot;index.js&quot;,
  6. &quot;scripts&quot;: {
  7. &quot;test&quot;: &quot;mocha --timeout 5000&quot;
  8. },
  9. &quot;author&quot;: &quot;&quot;,
  10. &quot;license&quot;: &quot;ISC&quot;,
  11. &quot;dependencies&quot;: {
  12. &quot;aws-sdk&quot;: &quot;^2.1373.0&quot;,
  13. &quot;chai&quot;: &quot;^4.3.7&quot;,
  14. &quot;mocha&quot;: &quot;^10.2.0&quot;,
  15. &quot;sinon&quot;: &quot;^15.0.4&quot;
  16. }
  17. }

OUTPUT
Mocking a connection call to AWS DynamoDB in sinon.

答案2

得分: 0

  1. # 在一个文件中分离DB连接
  2. 我们可以将DB连接分离到一个不同的文件中并在处理程序实现以及*spec*文件中导入它
  3. **db.js**
  4. ```js
  5. import AWS from &quot;aws-sdk&quot;;
  6. const db = new AWS.DynamoDB.DocumentClient({ apiVersion: &quot;2012-08-10&quot; });
  7. export default db;

yields() 函数

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

代码

  1. describe(&quot;user-handler connection success&quot;, function () {
  2. const sandbox = sinon.createSandbox();
  3. afterEach(() =&gt; sandbox.restore());
  4. before(() =&gt; {
  5. sinon.stub(db, &quot;put&quot;)
  6. .yields(null, true);
  7. sinon.stub(db, &quot;get&quot;)
  8. .yields(null, { sampleKey: &quot;sample value&quot; });
  9. sinon.stub(db, &quot;delete&quot;)
  10. .yields(null, { sampleKey: &quot;sample value&quot; });
  11. });
  12. after(() =&gt; {
  13. db.put.restore();
  14. db.get.restore();
  15. db.delete.restore();
  16. });
  17. it(&quot;Test saveUser() method success&quot;, async function () {
  18. const result = await userHandler.saveToken(&quot;sample user&quot;, {
  19. log: () =&gt; {},
  20. exception: () =&gt; {}
  21. });
  22. expect(result.statusCode).to.be.equal(201);
  23. });
  24. });

有用链接

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

  1. <details>
  2. <summary>英文:</summary>
  3. # Separating the DB connection in a file
  4. We can separate the DB connection in a different file and import it in the handler implementation as well as in the *spec* file.
  5. **db.js**
  6. ```js
  7. import AWS from &quot;aws-sdk&quot;;
  8. const db = new AWS.DynamoDB.DocumentClient({ apiVersion: &quot;2012-08-10&quot; });
  9. 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

  1. describe(&quot;user-handler connection success&quot;, function () {
  2. const sandbox = sinon.createSandbox();
  3. afterEach(() =&gt; sandbox.restore());
  4. before(() =&gt; {
  5. sinon.stub(db, &quot;put&quot;)
  6. .yields(null, true);
  7. sinon.stub(db, &quot;get&quot;)
  8. .yields(null, { sampleKey: &quot;sample value&quot; });
  9. sinon.stub(db, &quot;delete&quot;)
  10. .yields(null, { sampleKey: &quot;sample value&quot; });
  11. });
  12. after(() =&gt; {
  13. db.put.restore();
  14. db.get.restore();
  15. db.delete.restore();
  16. });
  17. it(&quot;Test saveUser() method success&quot;, async function () {
  18. const result = await userHandler.saveToken(&quot;sample user&quot;, {
  19. log: () =&gt; {},
  20. exception: () =&gt; {}
  21. });
  22. expect(result.statusCode).to.be.equal(201);
  23. });
  24. });

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:

确定