【问题标题】:How to mock/configure dynamodb in EXPRESS API如何在 EXPRESS API 中模拟/配置 dynamodb
【发布时间】:2021-10-24 07:28:43
【问题描述】:

我决定在我的 express API 中使用 jest 模拟 dynamodb。

查看各种示例 aws-sdk-mock https://stackoverflow.com/a/64568024/13126651

但我无法理解的是 -> “dynamodb 操作代码在快速路由中,并且测试文件是单独的,所以我如何编写一个模拟来确保测试快速路由代码但使用模拟我正在实施。”

我的 test.js

    const request = require('supertest');
    const app = require('../app');

//should i write my mock implementation over here?
    
    test('Should convert url from anonymous user ', async () => {
      await request(app)
        .post('/anon-ops/convert')
        .send({
          longUrl: 'https://google.com',
        })
        .expect(201);
    });

我的快速路由器(dynamodb 正在发布中)

//如何在此处模拟 dynamodb

const isUrl = require('is-url');
const AWS = require('aws-sdk');
const { nanoid } = require('nanoid/async');
const express = require('express');

const router = express.Router();
const dynamoDb = new AWS.DynamoDB.DocumentClient();

// URL from users

router.post('/', async (req, res, next) => {
  // urlId contains converted short url characters generated by nanoid

  const urlId = await nanoid(8);
  const { longUrl } = req.body;

  // Veryfying url Format using isUrl, this return a boolean
  const checkUrl = isUrl(longUrl);
  if (checkUrl === false) {
    res.status(400).json({ error: 'Invalid URL, please try again!!!' });
  }

  const originalUrl = longUrl;
  const userType = 'anonymous'; // user type for anonymous users
  const tableName = 'xxxxxxxxxxxxx'; // table name for storing url's

  const anonymousUrlCheckParams = {
    TableName: tableName,
    Key: {
      userId: userType,
      originalUrl,
    },
  };

  dynamoDb.get(anonymousUrlCheckParams, (err, data) => {
    const paramsForTransaction = {
      TransactItems: [
        {
          Put: {
            TableName: tableName,
            Item: {
              userId: userType,
              originalUrl,
              convertedUrl: `https://xxxxxxxxxxxxxxxx/${urlId}`,
            },
          },
        },

        {
          Put: {
            TableName: tableName,
            Item: {
              userId: urlId,
              originalUrl,
            },
            ConditionExpression: 'attribute_not_exists(userId)',
          },
        },
      ],
    };
    if (err) {
      console.log(err);
      res
        .status(500)
        .json({ error: 'Unknown Server Error, Please Trimify Again!' });
    } else if (Object.keys(data).length === 0 && data.constructor === Object) {
      dynamoDb.transactWrite(paramsForTransaction, async (error) => {
        if (error) {
          // err means converted value as userId is repeated twice.

          console.log(error);
          res
            .status(500)
            .json({ error: 'Unknown Server Error, Please trimify again. ' });
        } else {
          res.status(201).json({
            convertedUrl: `https://xxxxxxxxxxxx/${urlId}`,
          });
        }
      });
    } else {
      res.status(201).json({
        convertedUrl: data.Item.convertedUrl,
      });
    }
  });
});

module.exports = router;

【问题讨论】:

    标签: javascript node.js express jestjs mocking


    【解决方案1】:

    您可以使用jest.doMock(moduleName, factory, options) 模拟aws-sdk 模块。

    由于DocumentClient类是在模块范围内初始化的,如果多次需要同一个模块,模块会被缓存,这意味着模块范围内的mock对象也会被缓存。为了清除缓存,需要使用jest.resetModules()方法保证测试用例是隔离的,每个测试用例的mock对象不会影响其他测试用例。

    为了简单起见,我对代码进行了一些更改。

    例如

    server.js:

    const AWS = require('aws-sdk');
    const express = require('express');
    const bodyParser = require('body-parser');
    
    const app = express();
    const dynamoDb = new AWS.DynamoDB.DocumentClient();
    
    app.use(bodyParser.json());
    app.post('/anon-ops/convert', async (req, res) => {
      const urlId = 123;
      const { longUrl } = req.body;
      const originalUrl = longUrl;
      const userType = 'anonymous';
      const tableName = 'xxxxxxxxxxxxx';
    
      const anonymousUrlCheckParams = {
        TableName: tableName,
        Key: { userId: userType, originalUrl },
      };
    
      dynamoDb.get(anonymousUrlCheckParams, (err, data) => {
        if (err) {
          res.status(500).json({ error: 'Unknown Server Error, Please Trimify Again!' });
        } else if (Object.keys(data).length === 0 && data.constructor === Object) {
          const paramsForTransaction = {
            TransactItems: [
              {
                Put: {
                  TableName: tableName,
                  Item: { userId: userType, originalUrl, convertedUrl: `https://xxxxxxxxxxxxxxxx/${urlId}` },
                },
              },
              {
                Put: {
                  TableName: tableName,
                  Item: { userId: urlId, originalUrl },
                  ConditionExpression: 'attribute_not_exists(userId)',
                },
              },
            ],
          };
          dynamoDb.transactWrite(paramsForTransaction, (error) => {
            if (error) {
              res.status(500).json({ error: 'Unknown Server Error, Please trimify again. ' });
            } else {
              res.status(201).json({ convertedUrl: `https://xxxxxxxxxxxx/${urlId}` });
            }
          });
        } else {
          res.status(201).json({ convertedUrl: data.Item.convertedUrl });
        }
      });
    });
    
    module.exports = app;
    

    server.test.js:

    const request = require('supertest');
    
    describe('68901199', () => {
      beforeEach(() => {
        jest.resetModules();
      });
      test('should send converted url', (done) => {
        const mockedDocumentClient = {
          get: jest.fn(),
          transactWrite: jest.fn(),
        };
    
        const mockedDynamoDB = {
          DocumentClient: jest.fn(() => mockedDocumentClient),
        };
        jest.doMock('aws-sdk', () => {
          return { DynamoDB: mockedDynamoDB };
        });
    
        mockedDocumentClient.get.mockImplementation((params, callback) => {
          callback(null, {});
        });
        mockedDocumentClient.transactWrite.mockImplementation((params, callback) => {
          callback(null);
        });
        const app = require('./server');
        request(app)
          .post('/anon-ops/convert')
          .send({ longUrl: 'https://google.com' })
          .expect('Content-Type', /json/)
          .expect(201)
          .end((err, res) => {
            if (err) return done(err);
            expect(res.body).toEqual({ convertedUrl: `https://xxxxxxxxxxxx/123` });
            done();
          });
      });
    });
    

    测试结果:

     PASS  examples/68901199/server.test.js (10.195 s)
      68901199
        ✓ should send converted url (893 ms)
    
    -----------|---------|----------|---------|---------|-------------------
    File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
    -----------|---------|----------|---------|---------|-------------------
    All files  |      88 |     62.5 |     100 |    87.5 |                   
     server.js |      88 |     62.5 |     100 |    87.5 | 23,44,50          
    -----------|---------|----------|---------|---------|-------------------
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total
    Snapshots:   0 total
    Time:        10.893 s
    

    包版本:

    "supertest": "^6.0.1",
    "jest": "^26.6.3",
    "aws-sdk": "^2.875.0",
    

    【讨论】:

    • 感谢您的精彩回答,您能否解释一下 server.test,以便我也可以在我的其他项目中重现
    • 还有你在导出应用文件时为什么要唱const app = require('./server');
    • @JatinMehrotra 我没有使用express.Router(),我使用express()。只需简单地演示如何测试即可。
    • 明白了我也试过你的代码刚刚添加了 express.Router(),在运行你的代码时它给出了 500 个内部服务器错误
    猜你喜欢
    • 2020-07-25
    • 2018-03-17
    • 1970-01-01
    • 2011-10-22
    • 2021-11-20
    • 2021-08-10
    • 2021-03-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多