模拟Javascript AWS.RDS.Signer
我有一个连接类,用于通过IAM身份验证连接到AWS Rds代理。该过程的一部分是创建令牌。我有一个创建令牌的函数,但现在我很难模拟和测试它 下面是使用模拟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 =
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我还添加了一个简单的模拟同步