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

8
我已经为我的应用程序编写了一些包含各种业务逻辑的Meteor方法。现在我想为这些方法编写单元测试。我指的是快速测试,不会执行XHR或写入数据库。

  • 如何做到这一点?我当前的想法是,在测试配置中启动Meteor服务器时,使用虚拟集合(通过传递new Meteor.Collection(null))替换我的集合,并在服务器端运行我的单元测试,依次调用每个方法的Meteor.call()。我不太确定如何启动测试,可能我需要在我的应用程序中构建一个自定义的/tests URL 来触发它们。这种方法看起来合理吗?是否有任何可以使我的方法更容易编写单元测试的库/包?
2个回答

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()

首先,此代码仅在定义Meteor.settings ["test"]时运行,我可以在本地运行测试时执行此操作,但在生产中永远不应为真。然后,它会搜索“tests”目录下的Javascript或Coffeescript文件(我的实现不会搜索子目录,但很容易添加),并将它们添加到mocha实例中。我在这里使用了优秀的mocha Javascript测试库,结合chai断言库。
所有这些代码都包含在Meteor.startup调用中,以便我的单元测试在服务器启动时运行。这特别好,因为Meteor在我更改任何代码时自动重新运行我的测试。由于决定隔离数据库并且不执行XHR,因此我的测试只需要几毫秒就能运行完,所以这并不烦人。
对于测试本身,我需要执行以下操作:
chai = require("chai")
should = chai.should()

为了引入断言库。然而,仍有一些棘手的问题需要解决。首先,如果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())

下一个问题是为了测试目的,用模拟集合替换我的集合。如果您遵循标准Meteor实践将您的集合放在全局变量中,则这非常难以实现。但是,如果您将您的集合作为全局对象上的属性,则可以做到这一点。只需通过myApp.Collection = new Meteor.Collection("name")创建您的集合。然后,在测试中,您可以使用before函数模拟集合:
realCollection = null
before ->
  realCollection = myApp.Collection
  myApp.Collection = new Meteor.Collection(null)
after ->
  myApp.Collection = realCollection

这种方式可以在测试运行期间模拟您的集合,但然后它将被还原,以便您可以正常地与应用程序交互。通过类似的技术,还有其他一些东西可以进行模拟。例如,全局Meteor.userId()函数仅适用于客户端发起的请求。实际上,我已经向Meteor提交了错误,以查看他们是否能够提供更好的解决方案来解决这个问题,但目前我只是用自己的版本替换该函数进行测试:

realUserIdFn = null
before ->
  realUserIdFn = Meteor.userId
  Meteor.userId = -> "123456"
after ->
  Meteor.userId = realUserIdFn

这种方法可以用于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

0

我一直在使用jasmine来测试我正在开发的一个更大的Meteor应用程序。它不仅可以进行单元测试,还可以做更多的事情,并且一直运行得相当不错。我计划写一篇关于这个的博客文章,但现在我可以给你这个CoffeeScript代码:

if were_testing()
  describe 'something', ->
    it 'should be greater than 0', ->
      expect(theThing).toBeGreaterThan 0

were_testing = -> document.location.pathname.replace(/^\/([^\/]*).*$/, '$1') == 'tests'

jasmine_test = ->
  jasmineEnv = jasmine.getEnv()
  jasmineEnv.updateInterval = 1000

  htmlReporter = new jasmine.HtmlReporter()
  jasmineEnv.addReporter htmlReporter

  jasmineEnv.execute()

这段代码在浏览器中运行。如果您希望编写脚本,可以在casperjs实例中运行它。由于它通过客户端进行Meteor初始化,因此将执行XHR和数据库查询,但您可以轻松编写不执行任何额外查询的测试。或者编写一个子集函数,在访问/unittests时触发。

我们的东西尚未投入生产,但部署脚本仅删除上述jasmine代码和所有*.spec.coffee文件。

我想将其插入到Jenkins中,以实现适当的持续集成设置,但我还没有找到时间除了基础设置之外设置Jenkins。我还尝试使用casperjs进行无头浏览,效果还不错。

您也可以采用不同的方法,将测试代码放在tests/中(不会被Meteor执行),然后使用require。我快速尝试了一下,发现它相当繁琐。


我可能没有理解对,但是看起来这段代码会在浏览器中运行,对吗?如果是这样的话,我认为很难单独测试方法。除非有特殊情况,否则你的方法调用将会执行XHR和数据库写入等操作,对吧? - Derek Thurn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接