"no such table: users" 在测试和使用 Bookshelf / Knex 时

"no such table: users" when testing and using Bookshelf / Knex

我正在尝试 运行 使用 Mocha 和我的 Bookshelf / Knex 模型进行简单测试,但是我收到错误 "Unhandled rejection Error: SQLITE_ERROR: no such table: users"。请注意,我正在尝试在内存中使用 SQLite。

这是我的 Knexfile:

module.exports = {

  development: {
    client: 'sqlite3',
    connection: {
      filename: ':memory:'
    }
  },

  test: {
    client: 'mysql',
    connection: {
      host: '172.18.0.2',
      user: 'root',
      password: '',
      database: 'staging_db',
      charset  : 'utf8'
    },
    pool: {
      min: 2,
      max: 10
    },
    migrations: {
      tableName: 'knex_migrations'
    }
  },

  production: {
    client: 'mysql',
    connection: {
      host: process.env.DB_HOST || 'localhost',
      user: process.env.DB_USER || 'usr',
      password: process.env.DB_PWD || '',
      database: process.env.DB_NAME || 'db',
      charset  : 'utf8'
    },
    pool: {
      min: 2,
      max: 10
    },
    migrations: {
      tableName: 'knex_migrations'
    }
  }

};

Database.js:

var config      = require('../knexfile.js');  
var env         = process.env.NODE_ENV || 'development';  
var knex        = require('knex')(config[env]);

knex.migrate.latest([config]);

let bookshelf = require('bookshelf')(knex);

bookshelf.plugin('registry'); // Resolve circular dependencies with relations

// Export bookshelf for use elsewhere
module.exports = bookshelf;

我的用户模型:

let bookshelf = require('../config/database');

require('./role');

var User = bookshelf.Model.extend({
    tableName: 'users',
    role: function() {
        return this.hasOne(Role);
    }
});

module.exports = bookshelf.model('user', User);

我的测试:

var User = require('../../models/user'),
    chai = require('chai'),
    expect = chai.expect;

describe('User model', function () {

    it('should return empty set before adding anything', () => {
        expect(User.collection().count()).to.equal(0); 
    });

});

我是不是漏掉了什么?

更新

正在添加迁移文件:

exports.up = function(knex, Promise) {
  return knex.schema.createTable('roles', function (table) {
        //this creates an id column as auto incremented primary key
        table.increments();
        table.string('description', 45).notNullable();
    })
    .createTable('users', function (table) {
        table.increments();
        table.string('name', 60);
        table.string('password', 45);
        table.integer('role_id').unsigned();
        table.foreign('role_id').references('roles.id');
    });
};

exports.down = function(knex, Promise) {
  return knex.schema.dropTable('users').dropTable('roles');
};

首先:您的测试是错误的:count() returns 计数 promise 而不是计数本身。因此,将您的测试更改为:

it('should return empty set before adding anything', function(done) {
    User
    .collection()
    .count()
    .then(count => {
        expect(count).to.equal(0);
        done()
    })
    .catch(err => done(err)); 
});

其次:您在测试和迁移之间存在竞争条件。

如果您在 knexfile.js

上启用 Knex 调试模式
//...
development: {
  client: 'sqlite3',
  connection: {
    filename: ':memory:'
  },
  debug: true
},
//...

你会得到

$ ./node_modules/mocha/bin/_mocha ./test/user.js
Knex:warning - sqlite does not support inserting default values. Set the `useNullAsDefault` flag to hide this warning. (see docs http://knexjs.org/#Builder-insert).


  User model
[ { sql: 'select * from sqlite_master where type = \'table\' and name = ?',
    output: [Function: output],
    bindings: [ 'knex_migrations' ] } ]
[ { sql: 'create table if not exists "knex_migrations" ("id" integer not null primary key autoincrement, "name" varchar(255), "batch" integer, "migration_time" datetime)',
    bindings: [] } ]
{ method: 'select',
  options: {},
  timeout: false,
  cancelOnTimeout: false,
  bindings: [],
  __knexQueryUid: '7186321c-92b5-416c-8a5f-304118e47a0d',
  sql: 'select count(*) as "count" from "users"' }
[ { sql: 'select * from sqlite_master where type = \'table\' and name = ?',
    output: [Function: output],
    bindings: [ 'knex_migrations_lock' ] } ]
    1) should return empty set before adding anything
[ { sql: 'create table if not exists "knex_migrations_lock" ("is_locked" integer)',
    bindings: [] } ]

{ method: 'select',
  options: {},
  timeout: false,
  cancelOnTimeout: false,
  bindings: [],
  __knexQueryUid: '7ebd63cc-1070-4422-b664-47e28b58de15',
  sql: 'select * from "knex_migrations_lock"' }

  0 passing (62ms)
  1 failing

  1) User model should return empty set before adding anything:
     select count(*) as "count" from "users" - SQLITE_ERROR: no such table: users
  Error: SQLITE_ERROR: no such table: users

请注意,count() 查询发生在迁移仍在检查迁移表是否存在时。

一个简单(而且过于丑陋)的修复方法是在测试中添加一些延迟:

describe('User model', function () {

    this.timeout(20000);

    before(function (done) {
        setTimeout(() => done(), 200);  // 200ms was enough on my env
    })

    it('should return empty set before adding anything', function (done) {
        User.collection().count()
        .then(count => {
            expect(count).to.equal(0);
            done()
        })
        .catch(err => done(err)); 
    });

});

你会很高兴得到

$ ./node_modules/mocha/bin/_mocha ./test/user.js
Knex:warning - sqlite does not support inserting default values. Set the `useNullAsDefault` flag to hide this warning. (see docs http://knexjs.org/#Builder-insert).


  User model
    ✓ should return empty set before adding anything


  1 passing (225ms)

请注意,测试用了 200 多毫秒才到达 运行。

另一种可能的方法是将迁移添加到 before() 子句。这将使您能够对它们使用 await