用摩卡/西农嘲笑 es6 类构造函数属性



我有一个小的包装类,它为一些mysql功能添加了承诺。

const mysql = require('mysql');

export default class MySQL {
constructor(host, user, password, database, port = 3306) {
this.conn = mysql.createConnection({
host,
port,
user,
password,
database,
});
}
query(sql, args) {
return new Promise((resolve, reject) => {
// eslint-disable-next-line consistent-return
this.conn.query(sql, args, (err, rows) => {
if (err) {
reject(err);
return;
}
resolve(rows);
});
});
}
close() {
return new Promise((resolve, reject) => {
this.conn.end((err) => {
if (err) {
reject(err);
return;
}
resolve();
});
});
}
}

我正在尝试为这个类编写一个单元测试,但完全陷入了试图模拟this.conn的困境。

我已经尝试了代理奎尔,西农的各种组合,两者的组合。 当我在之前使用代理查询每个钩子时:

beforeEach(function () {
createConnectionStub = sinon.stub();
MySQL = proxyquire('../../lib/utils/mysql', {
mysql: {
createConnection: createConnectionStub,
},
}).default;
});

并尝试为 conn 对象设置存根:

it('Returns query results', async function () {
stubDb = new MySQL('host', 'user', 'password', 'database');
stubDb.conn = sinon.stub();
const results = await stubDb.query('SELECT * FROM whatever');
});

我一直在TypeError: this.conn.query is not a function

将模拟设置为 this.conn 属性的最佳方法是什么,以便我可以轻松地断言针对它的方法调用? 任何帮助将不胜感激

我迟到了一小时。 :)

但是我已经编写了示例并提供测试的替代方案,所以我继续发布这个。

我同意,您根本不需要代理查询。我在下面的示例中使用 sinon 沙箱、存根和假货。

// @file stackoverflow.js
const sinon = require('sinon');
const { expect } = require('chai');
const mysql = require('mysql');
// Change this to your mysql class definition.
const MySQL = require('./mysql.js');
describe('MySQL', function () {
let sandbox;
before(function () {
sandbox = sinon.createSandbox();
});
after(function () {
sandbox.restore();
});
it('constructor fn', function () {
// Prepare stub.
const stubMysql = sandbox.stub(mysql, 'createConnection');
// This just to make sure whether conn is storing this true value.
stubMysql.returns(true);
const test = new MySQL('host', 'user', 'password', 'database');
// Check whether call mysql.createConnection the right way.
expect(test).to.be.an('object');
expect(test).to.have.property('conn', true);
expect(stubMysql.calledOnce).to.equal(true);
expect(stubMysql.args[0]).to.have.lengthOf(1);
expect(stubMysql.args[0][0]).to.have.property('host', 'host');
expect(stubMysql.args[0][0]).to.have.property('user', 'user');
expect(stubMysql.args[0][0]).to.have.property('password', 'password');
expect(stubMysql.args[0][0]).to.have.property('database', 'database');
expect(stubMysql.args[0][0]).to.have.property('port', 3306);
// Restore stub.
stubMysql.restore();
});
it('query fn', async function () {
let fakeCounter = 0;
// Create fake function.
const fakeMysqlQuery = sinon.fake((arg1, arg2, arg3) => {
// On first response: return fake row.
if (fakeCounter === 0) {
fakeCounter += 1;
arg3(undefined, []);
}
// On second response: return error.
if (fakeCounter > 0) {
arg3(new Error('TESTQUERY'));
}
});
// Prepare stub.
const stubMysql = sandbox.stub(mysql, 'createConnection');
stubMysql.returns({
query: fakeMysqlQuery,
});
const test = new MySQL('host', 'user', 'password', 'database');
expect(test).to.be.an('object');
expect(test).to.have.property('conn');
expect(test.conn).to.respondTo('query');
expect(stubMysql.calledOnce).to.equal(true);
expect(test).to.respondTo('query');
// Test success query.
const results = await test.query('SELECT * FROM whatever');
expect(results).to.be.an('array');
expect(results).to.have.lengthOf(0);
expect(fakeMysqlQuery.calledOnce).to.equal(true);
expect(fakeMysqlQuery.args[0]).to.have.lengthOf(3);
expect(fakeMysqlQuery.args[0][0]).to.equal('SELECT * FROM whatever');
expect(fakeMysqlQuery.args[0][1]).to.be.an('undefined');
expect(fakeMysqlQuery.args[0][2]).to.be.an('function');
expect(fakeCounter).to.equal(1);
// Test rejection.
try {
await test.query('SELECT * FROM blablabla');
expect.fail('should not reach here for mysql query test.');
} catch (error) {
expect(error).to.have.property('message', 'TESTQUERY');
expect(fakeMysqlQuery.calledTwice).to.equal(true);
expect(fakeMysqlQuery.args[1]).to.have.lengthOf(3);
expect(fakeMysqlQuery.args[1][0]).to.equal('SELECT * FROM blablabla');
expect(fakeMysqlQuery.args[1][1]).to.be.an('undefined');
expect(fakeMysqlQuery.args[1][2]).to.be.an('function');
}
// Restore stub.
stubMysql.restore();
});
it('close fn', async function () {
let fakeCounter = 0;
// Create fake function.
const fakeMysqlEnd = sinon.fake((arg1) => {
// On first response: return fake row.
if (fakeCounter === 0) {
fakeCounter += 1;
arg1();
}
// On second response: return error.
if (fakeCounter > 0) {
arg1(new Error('TESTCLOSE'));
}
});
// Prepare stub.
const stubMysql = sandbox.stub(mysql, 'createConnection');
stubMysql.returns({
end: fakeMysqlEnd,
});
const test = new MySQL('host', 'user', 'password', 'database');
expect(test).to.be.an('object');
expect(test).to.have.property('conn');
expect(test.conn).to.respondTo('end');
expect(stubMysql.calledOnce).to.equal(true);
expect(test).to.respondTo('close');
// Test success close.
await test.close();
expect(fakeMysqlEnd.calledOnce).to.equal(true);
expect(fakeMysqlEnd.args[0]).to.have.lengthOf(1);
expect(fakeMysqlEnd.args[0][0]).to.be.an('function');
expect(fakeCounter).to.equal(1);
// Test failed close.
try {
await test.close();
expect.fail('should not reach here for mysql end test.');
} catch (error) {
expect(error).to.have.property('message', 'TESTCLOSE');
expect(fakeMysqlEnd.calledTwice).to.equal(true);
expect(fakeMysqlEnd.args[1]).to.have.lengthOf(1);
expect(fakeMysqlEnd.args[1][0]).to.be.an('function');
}
// Restore stub.
stubMysql.restore();
});
});
$ npx mocha stackoverflow.js 

MySQL
✓ constructor fn
✓ query fn
✓ close fn

3 passing (21ms)
$

希望这有帮助。

你不需要使用proxyquire模块,这个模块主要用于模拟/存根模块中的独立函数。单元测试应为:

index.js

const mysql = require('mysql');
export default class MySQL {
conn;
constructor(host, user, password, database, port = 3306) {
this.conn = mysql.createConnection({
host,
port,
user,
password,
database,
});
}
query(sql, args) {
return new Promise((resolve, reject) => {
// eslint-disable-next-line consistent-return
this.conn.query(sql, args, (err, rows) => {
if (err) {
reject(err);
return;
}
resolve(rows);
});
});
}
close() {
return new Promise((resolve, reject) => {
this.conn.end((err) => {
if (err) {
reject(err);
return;
}
resolve();
});
});
}
}

index.test.js

import MySQL from '.';
import sinon from 'sinon';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
chai.use(chaiAsPromised);
const expect = chai.expect;
const mysql = require('mysql');
describe('62124221', () => {
afterEach(() => {
sinon.restore();
});
it('should return query results', async () => {
const mRows = [1, 2];
const mConn = {
query: sinon.stub().callsFake((sql, args, callback) => {
callback(null, mRows);
}),
};
sinon.stub(mysql, 'createConnection').returns(mConn);
const db = new MySQL('host', 'user', 'password', 'database');
const actual = await db.query('select 1;', 'args');
expect(actual).to.be.deep.equal([1, 2]);
sinon.assert.calledWithExactly(mysql.createConnection, {
host: 'host',
port: 3306,
user: 'user',
password: 'password',
database: 'database',
});
sinon.assert.calledWithExactly(mConn.query, 'select 1;', 'args', sinon.match.func);
});
it('should return handle error', async () => {
const mError = new Error('network');
const mConn = {
query: sinon.stub().callsFake((sql, args, callback) => {
callback(mError);
}),
};
sinon.stub(mysql, 'createConnection').returns(mConn);
const db = new MySQL('host', 'user', 'password', 'database');
await expect(db.query('select 1;', 'args')).to.be.rejectedWith('network');
sinon.assert.calledWithExactly(mysql.createConnection, {
host: 'host',
port: 3306,
user: 'user',
password: 'password',
database: 'database',
});
sinon.assert.calledWithExactly(mConn.query, 'select 1;', 'args', sinon.match.func);
});
});

带有覆盖率报告的单元测试结果:

62124221
✓ should return query results
✓ should return handle error

2 passing (20ms)
----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |      60 |       60 |   57.14 |      60 |                   
index.ts |      60 |       60 |   57.14 |      60 | 29-35             
----------|---------|----------|---------|---------|-------------------

这里只演示如何测试query方法,close方法的测试方法是相同的。

最新更新