web-dev-qa-db-ja.com

Node.jsでMySQL(ORMなし)をモックする方法は?

Felixgeの Node.js クライアントでnode-mysqlを使用しています。 ORMを使用していません。

私はVowsでテストしており、おそらくSinonを使用してデータベースをモックできるようにしたいと考えています。 (node-mysqlを除いて)実際にはDAL自体は持っていないので、どうすればいいのかよくわかりません。私のモデルは、多くのゲッターを持つ単純なCRUDです。

これを達成する方法に関するアイデアはありますか?

42
Josh Smith

Sinonを使用すると、モックまたはスタブをモジュール全体に配置できます。たとえば、mysqlモジュールに関数queryがあるとします。

var mock;

mock = sinon.mock(require('mysql'))
mock.expects('query').with(queryString, queryParams).yields(null, rows);

queryStringqueryParamsは期待される入力です。 rowsは期待する出力です。

テスト中のクラスでmysqlが必要になり、queryメソッドを呼び出すと、それが遮断されてsinonによって検証されます。

テストの期待セクションには、次のものが必要です。

mock.verify()

ティアダウンでは、mysqlを通常の機能に戻す必要があります。

mock.restore()
36
kgilpin

Mysqlを使用する独自のクラスにデータベースを抽象化することをお勧めします。次に、require()を使用してロードする代わりに、そのクラスのインスタンスをモデルのコンストラクターに渡すことができます。

この設定により、モックdbインスタンスをユニットテストファイル内のモデルに渡すことができます。

ここに小さな例があります:

// db.js
var Db = function() {
   this.driver = require('mysql');
};
Db.prototype.query = function(sql, callback) {
   this.driver... callback (err, results);
}
module.exports = Db;

// someModel.js
var SomeModel = function (params) {
   this.db = params.db
}
SomeModel.prototype.getSomeTable (params) {
   var sql = ....
   this.db.query (sql, function ( err, res ) {...}
}
module.exports = SomeModel;

// in app.js
var db = new (require('./db.js'))();
var someModel = new SomeModel ({db:db});
var otherModel = new OtherModel ({db:db})

// in app.test.js
var db = {
   query: function (sql, callback) { ... callback ({...}) }
}
var someModel = new SomeModel ({db:db});
9

私はnode.jsに完全に精通しているわけではありませんが、従来のプログラミングの意味では、そのようなテストを実現するには、データアクセスメソッドから抽象化する必要があります。次のようなDALクラスを作成できませんでした:

var DataContainer = function () {
}

DataContainer.prototype.getAllBooks = function() {
    // call mysql api select methods and return results...
}

テストのコンテキストで、初期化中にgetAllBooksクラスにパッチを適用します。

DataContainer.prototype.getAllBooks = function() {
    // Here is where you'd return your mock data in whatever format is expected.
    return [];
}

テストコードが呼び出されると、getAllBooksは実際にmysqlを呼び出すのではなく、モックデータを返すバージョンに置き換えられます。繰り返しますが、node.jsに完全には精通していないため、これは大まかな概要です。

5
doogle

私は@kgilpinの答えから始まり、AWS LambdaでMysqlをテストするために次のようなもので終わりました:

const sinon = require('sinon');
const LambdaTester = require('lambda-tester');
const myLambdaHandler = require( '../../lambdas/myLambda' ).handler;
const mockMysql = sinon.mock(require('mysql'));
const chai = require('chai');
const expect = chai.expect;

describe('Database Write Requests', function() {

 beforeEach(() => {
   mockMysql.expects('createConnection').returns({
     connect: () => {
       console.log('Succesfully connected');
     },
     query: (query, vars, callback) => {
       callback(null, succesfulDbInsert);
     },
     end: () => {
       console.log('Connection ended');
     }
   });

 });
 after(() => {
   mockMysql.restore();
 });

 describe( 'A call to write to the Database with correct schema', function() {

   it( 'results in a write success', function() {

     return LambdaTester(myLambdaHandler)
       .event(anObject)
       .expectResult((result) => {
         expect(result).to.equal(succesfulDbInsert);
       });
   });
 });


 describe( 'database errors', function() {

   before(() => {
     mockMysql.expects('createConnection').returns({
       connect: () => {
         console.log('Succesfully connected');
       },
       query: (query, vars, callback) => {
         callback('Database error!', null);
       },
       end: () => {
         console.log('Connection ended');
       }
     });
   });

   after(() => {
     mockMysql.restore();
   });

   it( 'results in a callback error response', function() {


     return LambdaTester(myLambdaHandler)
       .event(anObject)
       .expectError((err) => {
         expect(err.message).to.equal('Something went wrong');
       });
   });
 });
});

実際のデータベース接続は必要ないので、すべてのmysql応答を手動でモックしました。
.returnsに別の関数を追加すると、createConnectionから任意のメソッドをモックできます。

5
cameck

horaa を使用して外部依存関係をモックアウトできます

また、felixgeのノード sandboxed-module でも同様のことができると思います。

したがって、kgilpinと同じコンテキストを使用すると、ホラアでは次のようになります。

var mock = horaa('mysql');
mock.Hijack('query', function(queryString, queryParam) {
    // do your fake db query (e.g., return fake expected data)
});

//SUT calls and asserts

mock.restore('query');
3
dule

Mysqlドライバーを使用するには、最初に接続を作成し、返された接続コントローラーのAPIを使用する必要があるため、2ステップのアプローチが必要です。

それには2つの方法があります。

createConnectionをスタブし、スタブされた接続を返すようにする

セットアップ中:

const sinon = require('sinon');
const mysql = require('mysql');
const {createConnection} = mysql;
let mockConnection;
sinon.stub(mysql, 'createConnection').callsFake((...args) => {
    mockConnection = sinon.stub(createConnection.apply(mysql, args))
      .expects('query').withArgs(.... )//program it how you like :)
    return mockConnection;
})

const mockConnectionFactory = 
  sinon.stub(mysql)
  .expects('createConnection')

ティアダウン中:

mysql.createConnection.restore();

ここで、queryメソッドはインスタンスでモック化されており、基礎となるメカニズムには影響がないため、createConnectionのみを復元する必要があることに注意してください。

接続プロトタイプで.queryメソッドをスタブする

mysqlドライバーはインポート用の接続であることを公式に公開していないため、この手法はもう少しトリッキーです。 (まあ、接続を実装するモジュールだけをインポートすることもできますが、リファクタリングによってそこから移動されないという保証はありません)。したがって、プロトタイプへの参照を取得するには、通常、接続を作成し、コンストラクター-プロトタイプチェーンをたどります。

通常は1行で行いますが、手順に分けてここで説明します。

セットアップ中:

const realConnection = mysql.createConnection({})
const mockTarget = realConnection.constructor.prototype;
//Then - brutally
consdt mock = sinon.mock(mockTarget).expect('query'....
//OR - as I prefer the surgical manner
sinon.stub(mockTarget, 'query').expect('query'....

ティアダウン中

//brutal
mock.restore()
// - OR - surgical:
mockTarget.query.restore()

ここではcreateConnectionメソッドをモックしないことに注意してください。すべての接続パラメーターの検証は引き続き行われます(これを実現させたいと思います。私は真正な部品を最大限に使用して作業することを望んでいます。したがって、高速なテストを行うために必要な最低限のものを模倣します)。ただし、queryはプロトタイプにモックされており、復元する必要があります。

また、外科的に作業する場合、verifyはmockTargetではなく、mockedメソッドにあることに注意してください。

これについての良いリソースは次のとおりです: http://devdocs.io/sinon~6-stubs/

2