我为我的应用程序编写了一些包含各种业务逻辑块的Meteor方法。现在我想为这些方法编写单元测试。通过单元测试,我特别指的是没有的快速测试:
我该怎么做呢?我目前的想法是,当我在测试配置中启动Meteor服务器时,我应该用虚拟集合替换我的集合(通过传递new Meteor.Collection(null)
)并在服务器端运行我的单元测试,调用Meteor.call()从那里依次对我的每一个方法。我不完全确定我是如何启动测试的,可能我想在我的应用程序中构建一个自定义/tests
URL,然后将其激活。这种方法看起来合理吗?是否有任何库/包可以使我的方法的单元测试更容易?
答案 0 :(得分:1)
好的,这就是我提出的单元测试方法。我会是第一个承认这有很大改进空间的人!
首先,在我的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方法调用没有被光纤包裹,它们将会失败。我目前没有很好的解决这个问题的方法,但我创建了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提出a bug,看看他们是否可以为这个问题提供更好的解决方案,但是现在我只是用我自己的版本替换该功能进行测试:
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
答案 1 :(得分:0)
我一直在使用jasmine来测试我正在研究的更大的Meteor应用程序。它可以做的不仅仅是单元测试,而且它运行得相当好。关于这个的博客文章是关于我的todo lis。现在我可以给你这个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时触发的函数子集
我们的东西还没有投入生产,但是deployscript只是删除了上面的jasmine代码和所有*.spec.coffee
个文件。
我想将其插入Jenkins以获得正确的持续集成设置,但没有时间设置Jenkins而不是基础知识。我也使用casperjs进行无头浏览,效果非常好。
您还可以将测试代码放在tests/
(不由Meteor执行)中,然后使用require
,从而采用不同的方法。我很快就尝试了这一点,发现它很乏味。