模拟Javascript AWS.RDS.Signer

模拟Javascript AWS.RDS.Signer,javascript,node.js,amazon-web-services,aws-sdk-mock,Javascript,Node.js,Amazon Web Services,Aws Sdk Mock,我有一个连接类,用于通过IAM身份验证连接到AWS Rds代理。该过程的一部分是创建令牌。我有一个创建令牌的函数,但现在我很难模拟和测试它 下面是使用setToken方法的连接类: class Connection { constructor(username, endpoint, database) { this.username = username; this.endpoint = endpoint; this.database =

我有一个连接类,用于通过IAM身份验证连接到AWS Rds代理。该过程的一部分是创建令牌。我有一个创建令牌的函数,但现在我很难模拟和测试它

下面是使用
setToken
方法的连接类:

class Connection {
    constructor(username, endpoint, database) {
        this.username = username;
        this.endpoint = endpoint;
        this.database = database;
    }

    setToken () {
        let signer = new AWS.RDS.Signer({
            region: 'us-east-1', // example: us-east-2
            hostname: this.endpoint,
            port: 3306,
            username: this.username
        });

        this.token = signer.getAuthToken({
            username: this.username
        });
    }
}
这里我试图模拟
AWS.RDS.Signer.getAuthToken()的返回值

我希望看到“mock token”作为
conn.token
的值,但我得到的是:

{
  promise: [Function],
  createReadStream: [Function: createReadStream],
  on: [Function: on],
  send: [Function: send]
}
如何获取
AWS.RDS.Signer.getAuthToken()
以返回模拟令牌


从@ggordon尝试解决方案后编辑

我试图通过将
AWS
注入构造函数来实现这一点,但似乎仍然存在同样的问题。我认为我的部分问题是AWS.RDS.Signer不支持承诺,但我不能完全确定

这是我的新代码:

生成令牌的
令牌
类。 从“AWS sdk”导入AWS

class Token {
    constructor(awsInstance) {
        this.awsInstance = awsInstance || AWS;
    }

    getToken () {
        const endpoint = 'aurora-proxy.proxy.rds.amazonaws.com';

        const signer = new this.awsInstance.RDS.Signer({
            region: 'my-region',
            hostname: endpoint,
            port: 3306,
            username: 'myUser'
        });

        const token = signer.getAuthToken({
                username: 'svcLambda'
            });

        console.log ("IAM Token obtained\n");
        return token
    }
}

module.exports = { Token };
以及测试:

test('Should test getToken from Token', async () => {
    AWSMock.setSDKInstance(AWS);
    AWSMock.mock('RDS.Signer', 'getAuthToken', 'mock-token');

    let tokenObject = new tokens.Token(AWS);
    const token = tokenObject.getToken();

    console.log(token);
    expect(token).toStrictEqual('mock-token');
});
Token类本身可以工作——它创建了Token,并且可以使用该Token成功连接到RDS。但是,单元测试失败,返回的实际令牌(来自console.log)如下:

{
  promise: [Function],
  createReadStream: [Function: createReadStream],
  on: [Function: on],
  send: [Function: send]
}
这里还有@GSSWain请求的
package.json

{
  "name": "mylambda",
  "version": "0.0.1",
  "description": "My description.",
  "repository": {
    "type": "git",
    "url": ""
  },
  "scripts": {
    "lint": "eslint src/**/*.js __tests__/**/*.js",
    "prettier": "prettier --write src/**/*.js __tests__/**/*.js",
    "prettier:ci": "prettier --list-different src/**/*.js  __tests__/**/*.js",
    "test": "cross-env NODE_ENV=test jest",
    "test:coverage": "cross-env CI=true jest --coverage --watchAll=false -u --reporter=default --reporters=jest-junit",
    "build": "npm run build:dev",
    "build:dev": "cross-env NODE_ENV=development webpack --config webpack.config.js"
  },
  "dependencies": {
    "mysql2": "^2.2.5"
  },
  "devDependencies": {
    "@babel/core": "^7.6.4",
    "@babel/preset-env": "^7.6.3",
    "aws-sdk": "^2.552.0",
    "aws-sdk-mock": "^5.1.0",
    "babel-jest": "^24.9.0",
    "babel-loader": "^8.0.6",
    "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
    "cross-env": "^6.0.3",
    "eslint": "^6.5.1",
    "eslint-config-prettier": "^6.4.0",
    "eslint-plugin-jest": "^22.19.0",
    "jest": "^24.9.0",
    "jest-junit": "^10.0.0",
    "prettier": "^1.18.2",
    "sinon": "^9.0.3"
  },
  "jest": {
    "verbose": true,
    "transform": {
      "^.+\\.js$": "babel-jest"
    },
    "globals": {
      "NODE_ENV": "test"
    },
    "moduleFileExtensions": [
      "js"
    ],
    "moduleDirectories": [
      "node_modules",
      "src"
    ],
    "coverageThreshold": {
      "global": {
        "statements": 100,
        "branches": 100,
        "functions": 100,
        "lines": 100
      }
    }
  },
  "jest-junit": {
    "outputName": "junit_jest.xml"
  }
}
问题 测试范围中的
AWS
实例/对象与
setToken
方法中使用的
AWS
实例/对象不同

模拟此实例

由于传输,用TypeScript或ES6编写的代码可能无法正确模拟,因为在aws sdk模拟中创建的aws sdk对象将不等于在要测试的代码中创建的对象

另外,
require
将返回一个新实例

本质上,您是在测试中模拟一个实例,而您的实际代码正在使用另一个未被模拟的实例

可能的解决方案 解决方案1 您可以修改您的代码,允许您有选择地注入所需的
AWS
实例以使用例如

import AWS from 'aws-sdk';
class Connection {
    constructor(username, endpoint, database,awsInstance) {
        this.username = username;
        this.endpoint = endpoint;
        this.database = database;
        //if the awsInstance is null  or not provided use the default
        this.awsInstance = awsInstance || AWS;
    }

    setToken () {
        let signer = new this.awsInstance.RDS.Signer({
            region: 'us-east-1', // example: us-east-2
            hostname: this.endpoint,
            port: 3306,
            username: this.username
        });

        this.token = signer.getAuthToken({
            username: this.username
        });
    }
}
您的代码不需要任何修改,但是现在您可以选择在测试中进行修改

test('Test Connection setToken', async () => {
    AWSMock.setSDKInstance(AWS);
    AWSMock.mock('RDS.Signer', 'getAuthToken', 'mock-token');


    let conn = new connections.Connection(
        'testUser',
        'testEndpoint',
        'testDb',
        AWS //pass mock instance
        );

    conn.setToken();
    let actualToken = (await conn.token.promise());

    console.log(conn.token);
    console.log(actualToken);
});
这是唯一基于构造函数的注入,您可以通过在
setToken
方法中执行类似操作来注入它

您还将注意到,在
aws sdk mock
提供的示例和上面的示例中,我们从返回的
promise
对象中提取了结果。这是因为,尽管aws sdk特别支持同步操作,但仍返回承诺对象这是一个基于您正在使用的库的约束。

解决方案2

您可能想考虑另一个模拟库,如果您对同步调用感兴趣的话,基于这里共享的示例可以更好地模拟代码/流。另一种选择是考虑异步/承诺重写您的实现。我把这个决定留给你

一个简单的替代方案可以是:

test('Test Connection setToken', async () => {
    AWS.RDS.Signer = function MockSigner() {
        return {
            getAuthToken: function MockGetAuthToken(){ return 'mock-token'; }
        };
    };


    let conn = new connections.Connection(
        'testUser',
        'testEndpoint',
        'testDb',
        AWS //pass mock instance
        );

    conn.setToken();

    console.log(conn.token);
});
其他参考资料 我已经包含了一个用于模拟从中检索到的
aws sdk mock
函数的方法片段。您将看到它创建并返回一个
请求

function mockServiceMethod(service, client, method, replace) {
  services[service].methodMocks[method].stub = sinon.stub(client, method).callsFake(function() {
    const args = Array.prototype.slice.call(arguments);

    let userArgs, userCallback;
    if (typeof args[(args.length || 1) - 1] === 'function') {
      userArgs = args.slice(0, -1);
      userCallback = args[(args.length || 1) - 1];
    } else {
      userArgs = args;
    }
    const havePromises = typeof AWS.Promise === 'function';
    let promise, resolve, reject, storedResult;
    const tryResolveFromStored = function() {
      if (storedResult && promise) {
        if (typeof storedResult.then === 'function') {
          storedResult.then(resolve, reject)
        } else if (storedResult.reject) {
          reject(storedResult.reject);
        } else {
          resolve(storedResult.resolve);
        }
      }
    };
    const callback = function(err, data) {
      if (!storedResult) {
        if (err) {
          storedResult = {reject: err};
        } else {
          storedResult = {resolve: data};
        }
      }
      if (userCallback) {
        userCallback(err, data);
      }
      tryResolveFromStored();
    };
    const request = {
      promise: havePromises ? function() {
        if (!promise) {
          promise = new AWS.Promise(function (resolve_, reject_) {
            resolve = resolve_;
            reject = reject_;
          });
        }
        tryResolveFromStored();
        return promise;
      } : undefined,
      createReadStream: function() {
        if (replace instanceof Readable) {
          return replace;
        } else {
          const stream = new Readable();
          stream._read = function(size) {
            if (typeof replace === 'string' || Buffer.isBuffer(replace)) {
              this.push(replace);
            }
            this.push(null);
          };
          return stream;
        }
      },
      on: function(eventName, callback) {
      },
      send: function(callback) {
      }
    };

    // different locations for the paramValidation property
    const config = (client.config || client.options || _AWS.config);
    if (config.paramValidation) {
      try {
        // different strategies to find method, depending on wether the service is nested/unnested
        const inputRules =
          ((client.api && client.api.operations[method]) || client[method] || {}).input;
        if (inputRules) {
          const params = userArgs[(userArgs.length || 1) - 1];
          new _AWS.ParamValidator((client.config || _AWS.config).paramValidation).validate(inputRules, params);
        }
      } catch (e) {
        callback(e, null);
        return request;
      }
    }

    // If the value of 'replace' is a function we call it with the arguments.
    if (typeof replace === 'function') {
      const result = replace.apply(replace, userArgs.concat([callback]));
      if (storedResult === undefined && result != null &&
          typeof result.then === 'function') {
        storedResult = result
      }
    }
    // Else we call the callback with the value of 'replace'.
    else {
      callback(null, replace);
    }
    return request;
  });
}
问题 测试范围中的
AWS
实例/对象与
setToken
方法中使用的
AWS
实例/对象不同

模拟此实例

由于传输,用TypeScript或ES6编写的代码可能无法正确模拟,因为在aws sdk模拟中创建的aws sdk对象将不等于在要测试的代码中创建的对象

另外,
require
将返回一个新实例

本质上,您是在测试中模拟一个实例,而您的实际代码正在使用另一个未被模拟的实例

可能的解决方案 解决方案1 您可以修改您的代码,允许您有选择地注入所需的
AWS
实例以使用例如

import AWS from 'aws-sdk';
class Connection {
    constructor(username, endpoint, database,awsInstance) {
        this.username = username;
        this.endpoint = endpoint;
        this.database = database;
        //if the awsInstance is null  or not provided use the default
        this.awsInstance = awsInstance || AWS;
    }

    setToken () {
        let signer = new this.awsInstance.RDS.Signer({
            region: 'us-east-1', // example: us-east-2
            hostname: this.endpoint,
            port: 3306,
            username: this.username
        });

        this.token = signer.getAuthToken({
            username: this.username
        });
    }
}
您的代码不需要任何修改,但是现在您可以选择在测试中进行修改

test('Test Connection setToken', async () => {
    AWSMock.setSDKInstance(AWS);
    AWSMock.mock('RDS.Signer', 'getAuthToken', 'mock-token');


    let conn = new connections.Connection(
        'testUser',
        'testEndpoint',
        'testDb',
        AWS //pass mock instance
        );

    conn.setToken();
    let actualToken = (await conn.token.promise());

    console.log(conn.token);
    console.log(actualToken);
});
这是唯一基于构造函数的注入,您可以通过在
setToken
方法中执行类似操作来注入它

您还将注意到,在
aws sdk mock
提供的示例和上面的示例中,我们从返回的
promise
对象中提取了结果。这是因为,尽管aws sdk特别支持同步操作,但仍返回承诺对象这是一个基于您正在使用的库的约束。

解决方案2

您可能想考虑另一个模拟库,如果您对同步调用感兴趣的话,基于这里共享的示例可以更好地模拟代码/流。另一种选择是考虑异步/承诺重写您的实现。我把这个决定留给你

一个简单的替代方案可以是:

test('Test Connection setToken', async () => {
    AWS.RDS.Signer = function MockSigner() {
        return {
            getAuthToken: function MockGetAuthToken(){ return 'mock-token'; }
        };
    };


    let conn = new connections.Connection(
        'testUser',
        'testEndpoint',
        'testDb',
        AWS //pass mock instance
        );

    conn.setToken();

    console.log(conn.token);
});
其他参考资料 我已经包含了一个用于模拟从中检索到的
aws sdk mock
函数的方法片段。您将看到它创建并返回一个
请求

function mockServiceMethod(service, client, method, replace) {
  services[service].methodMocks[method].stub = sinon.stub(client, method).callsFake(function() {
    const args = Array.prototype.slice.call(arguments);

    let userArgs, userCallback;
    if (typeof args[(args.length || 1) - 1] === 'function') {
      userArgs = args.slice(0, -1);
      userCallback = args[(args.length || 1) - 1];
    } else {
      userArgs = args;
    }
    const havePromises = typeof AWS.Promise === 'function';
    let promise, resolve, reject, storedResult;
    const tryResolveFromStored = function() {
      if (storedResult && promise) {
        if (typeof storedResult.then === 'function') {
          storedResult.then(resolve, reject)
        } else if (storedResult.reject) {
          reject(storedResult.reject);
        } else {
          resolve(storedResult.resolve);
        }
      }
    };
    const callback = function(err, data) {
      if (!storedResult) {
        if (err) {
          storedResult = {reject: err};
        } else {
          storedResult = {resolve: data};
        }
      }
      if (userCallback) {
        userCallback(err, data);
      }
      tryResolveFromStored();
    };
    const request = {
      promise: havePromises ? function() {
        if (!promise) {
          promise = new AWS.Promise(function (resolve_, reject_) {
            resolve = resolve_;
            reject = reject_;
          });
        }
        tryResolveFromStored();
        return promise;
      } : undefined,
      createReadStream: function() {
        if (replace instanceof Readable) {
          return replace;
        } else {
          const stream = new Readable();
          stream._read = function(size) {
            if (typeof replace === 'string' || Buffer.isBuffer(replace)) {
              this.push(replace);
            }
            this.push(null);
          };
          return stream;
        }
      },
      on: function(eventName, callback) {
      },
      send: function(callback) {
      }
    };

    // different locations for the paramValidation property
    const config = (client.config || client.options || _AWS.config);
    if (config.paramValidation) {
      try {
        // different strategies to find method, depending on wether the service is nested/unnested
        const inputRules =
          ((client.api && client.api.operations[method]) || client[method] || {}).input;
        if (inputRules) {
          const params = userArgs[(userArgs.length || 1) - 1];
          new _AWS.ParamValidator((client.config || _AWS.config).paramValidation).validate(inputRules, params);
        }
      } catch (e) {
        callback(e, null);
        return request;
      }
    }

    // If the value of 'replace' is a function we call it with the arguments.
    if (typeof replace === 'function') {
      const result = replace.apply(replace, userArgs.concat([callback]));
      if (storedResult === undefined && result != null &&
          typeof result.then === 'function') {
        storedResult = result
      }
    }
    // Else we call the callback with the value of 'replace'.
    else {
      callback(null, replace);
    }
    return request;
  });
}

你能不能共享package.json文件你能不能共享package.json文件这对我还是不起作用
console.log
输出一个对象,而不是“模拟令牌”字符串。但是,我尝试了console.log(conn.token.promise(),输出为{'mock token'}。它没有通过单元测试
expect(token.toStrictEqual('mock-token');
Expected:{}
aws sdk mock的文档
和简短的代码审查表明,aws mock总是返回承诺对象,因此带有承诺的示例,请在测试中使用
expect(wait token.promise()).toStrictEqual('mock-token')
。@navig8tr此库模拟所有函数以返回承诺-请参阅实现-(@navig8tr我还添加了一个简单的模拟同步