我如何对我的流星方法进行单元测试?

Der*_*urn 8 database unit-testing meteor

我为我的应用程序编写了一些包含各种业务逻辑块的Meteor方法.现在我想为这些方法编写单元测试.通过单元测试,我特别指的是没有的快速测试:

  • 执行XHR或
  • 写入数据库

我该怎么做呢?我目前的想法是,当我在测试配置中启动Meteor服务器时,我应该用虚拟集合替换我的集合(通过传递new Meteor.Collection(null))并在服务器端运行我的单元测试,在我的每个方法上调用Meteor.call()从那里反过来.我不完全确定我是如何开始测试的,可能我想/tests在我的应用程序中构建一个自定义URL来激活它们.这种方法看起来合理吗?是否有任何库/包可以使我的方法的单元测试更容易?

Der*_*urn 2

好吧,这就是我对我的方法进行单元测试的想法。我将是第一个承认这方面还有很大改进空间的人!

首先,在我的server.coffee文件中,我有以下代码:

Meteor.startup ->
  return unless Meteor.settings["test"]
  require = __meteor_bootstrap__.require
  require("coffee-script")
  fs = require("fs")
  path = require("path")
  Mocha = require("mocha")

  mocha = new Mocha()
  files = fs.readdirSync("tests")
  basePath = fs.realpathSync("tests")
  for file in files
    continue unless file.match(/\.coffee$/) or file.match(/\.js$/)
    continue if file[0] == "."
    filePath = path.join(basePath, file)
    continue unless fs.statSync(filePath).isFile()
    mocha.addFile(filePath)
  mocha.run()
Run Code Online (Sandbox Code Playgroud)

首先,此代码仅在定义了 Meteor.settings["test"] 时运行,我可以在本地运行测试时执行此操作,但在生产中永远不应该如此。然后,它在“tests”目录中搜索 javascript 或 Coffeescript 文件(在我的实现中不会搜索子目录,但添加子目录很容易)并将它们添加到实例中mocha。我在这里使用优秀的mocha javascript 测试库,结合chai断言库。

所有这些代码都包含在一个Meteor.startup调用中,以便我的单元测试在服务器启动时运行。这特别好,因为每当我更改任何代码时,Meteor 都会自动重新运行我的测试。由于决定隔离数据库而不执行 XHR,我的测试在几毫秒内运行,因此这并不是很烦人。

对于测试本身,我需要做

chai = require("chai")
should = chai.should()
Run Code Online (Sandbox Code Playgroud)

拉入断言库。不过,仍有一些棘手的问题需要解决。首先,如果 Meteor 方法没有包装在 Fiber 中,它们的调用将会失败。我目前没有很好的解决这个问题的方法,但我创建了一个itShould函数来替换 mocha 的it函数并将测试主体包裹在 Fiber 中:

# A version of mocha's "it" function which wraps the test body in a Fiber.
itShould = (desc, fn) ->
  it(("should " + desc), (done) -> (Fiber ->
    fn()
    done()).run())
Run Code Online (Sandbox Code Playgroud)

接下来的问题是,出于测试目的,用模拟集合替换我的集合。如果您遵循将集合放入全局变量的标准 Meteor 实践,那么这是非常困难的。但是,如果您在全局对象上设置集合属性,则可以做到这一点。只需通过 即可进行收藏myApp.Collection = new Meteor.Collection("name")。然后,在您的测试中,您可以使用一个before函数模拟该集合:

realCollection = null
before ->
  realCollection = myApp.Collection
  myApp.Collection = new Meteor.Collection(null)
after ->
  myApp.Collection = realCollection
Run Code Online (Sandbox Code Playgroud)

这样,您的集合会在测试运行期间被模拟出来,但随后会被恢复,以便您可以正常与应用程序交互。其他一些事情可以通过类似的技术来模拟。例如,全局Meteor.userId()函数仅适用于客户端发起的请求。我实际上已经针对 Meteor 提交了一个错误,看看他们是否可以为这个问题提供更好的解决方案,但现在我只是用我自己的版本替换该函数进行测试:

realUserIdFn = null
before ->
  realUserIdFn = Meteor.userId
  Meteor.userId = -> "123456"
after ->
  Meteor.userId = realUserIdFn
Run Code Online (Sandbox Code Playgroud)

这种方法适用于 Meteor 的某些部分,但不适用于全部。例如,我还没有找到一种方法来测试调用的方法this.setUserId,因为我认为没有一个好的方法来模拟该行为。不过,总的来说,这种方法对我来说很有效……我喜欢在更改代码时能够自动重新运行测试,并且单独运行测试通常是一个好主意。服务器上的测试可以阻塞,这也非常方便,使得它们在没有回调链的情况下更容易编写。测试如下:

describe "the newWidget method", ->
  itShould "make a new widget in the Widgets collection", ->
    widgetId = Meteor.call("newWidget", {awesome: true})
    widget = myApp.Widgets.findOne(widgetId)
    widget.awesome.should.be.true
Run Code Online (Sandbox Code Playgroud)