// create a basic fake, with no behavior var fake = sinon.fake()
fake() // undefined
fake.callCount // 1
Creating a fake with custom behaviour
1 2 3 4 5
// create a fake that returns the text "foo" var fake = sinon.fake.returns('foo')
fake() // foo
Fakes 的行为
行为一旦创造,就无法改变。
sinon.fake.returns(value);
创建返回值参数的 fake。
1 2 3 4
var fake = sinon.fake.returns('apple pie')
fake() // apple pie
sinon.fake.throws(value);
创建一个 fake,该 fake 将使用提供的值作为 message 属性抛出 Error。
如果将 Error 作为 value 参数传递,则将作为抛出的值。 如果传递了任何其他值,则该值将用于引发 Error 的 message 属性。
1 2 3 4
var fake = sinon.fake.throws(newError('not apple pie'))
fake() // Error: not apple pie
sinon.fake.resolves(value);
Creates a fake that returns a resolved Promise for the passed value.
sinon.fake.rejects(value);
Creates a fake that returns a rejected Promise for the passed value. 如果错误被作为值参数传递,那么这将是 promise 的价值。 如果传递了其他任何值,则该值将用于 promise 返回的 Error 的 message 属性。
sinon.fake.yields([value1, …, valueN]);
sinon.fake.yields takes some values, and returns a function that when being called, expects the last argument to be a callback and invokes that callback with the same previously given values. The returned function is normally used to fake a service function that takes a callback as the last argument.
In code example below, the ‘readFile’ function of the ‘fs’ module is replaced with a fake function created by sinon.fake.yields. When the fake function is called, it always calls the last argument it received, which is expected to be a callback, with the values that the yields function previously took.
1 2 3 4 5 6 7 8
var fake = sinon.fake.yields(null, 'file content') sinon.replace(fs, 'readFile', fake) fs.readFile('somefile', (err, data) => { console.log(data) }) console.log('end of this event loop') // file content // end of this event loop
sinon.fake.yieldsAsync([value1, …, valueN]);
Similar to yields, yieldsAsync also returns a function that when invoked, the function expects the last argument to be a callback and invokes that callback with the same previously given values. However, the returned function invokes that callback asynchronously rather than immediately, i.e. in the next event loop.
Compare the output of the code example below with the output of the code example above for yields to see the difference.
1 2 3 4 5 6 7 8
var fakeAsync = sinon.fake.yieldsAsync(null, 'file content') sinon.replace(fs, 'readFile', fakeAsync) fs.readFile('somefile', (err, data) => { console.log(data) }) console.log('end of this event loop') // end of this event loop // file content
sinon.fake(func);
Wraps an existing Function to record all interactions, while leaving it up to the func to provide the behavior.
The created fake Function has the same API as a sinon.spy.
This is useful when complex behavior not covered by the sinon.fake.* methods is required or when wrapping an existing function or method.
Instance properties
The instance properties are the same as a sinon.spy.
f.callback
This property is a convenience to get a reference to the last callback passed in the last to the fake.
1 2 3 4 5 6 7 8 9
var f = sinon.fake() var cb1 = function () {} var cb2 = function () {}
TL;DR: Mocha is a $150k Porsche Panamera when the best tool for the job is a $30k Tesla Model 3. Don’t waste your resources on testing bells and whistles. Invest them in creating your app, instead. >总结: Mocha 是一个价值 150 刀的保时捷,但是往往我们工作中只需要 30 刀的特斯拉就能带来最好的效益。不要为了使用一个测试软件而去使用测试软件,而是使用他们来驱动你的程序开发。 UPDATE: I have written a simplified wrapper around Tape to make it even simpler to write great unit tests which provide clear bug reports when they fail. See “Rethinking Unit Test Assertions” for more details. UPDATE:我对 Tape 进行了简易封装,使它能够更容易的编写出优秀的单元测试,且能够在测试用例未通过时提供清晰的错误报告,有关其更多信息请参考 “Rethinking Unit Test Assertions”
Sometimes popularity is an indication of quality. Other times, popular things are popular for popularity’s sake, and not because they’re better than alternatives.
On real production projects, I have used Jasmine, Mocha, NodeUnit, Tape, and a bunch of other solutions. I have investigated many other options. For the last few years, I have used and continue to use Tape along with Supertest (for API testing) on all of my personal projects and projects that I lead.
在实际的生产项目中,我曾使用过 Jasmine,Mocha,NodeUnit,Tape 和许多其他解决方案。我研究过各种框架。但是在过去的几年中,我在我所有的个人项目和我主导的项目中都使用并继续使用Tape和 Supertest(用于 API 测试)。
What are Unit tests?
什么是单元测试?
Unit tests exist to test individual units of software functionality. A unit is a module, component, or function. They’re bits of the program that can work independently of the rest of the program. The presence of unit testing implies that the software is designed in a modular fashion. You may hear once in a while that there are ways to make software “more testable.”
If you find that it’s hard to write unit tests for your program without mocking lots of other things, that’s a sign that your program is not modular enough. Revealing tight coupling (the opposite of modularity) is one of the many important roles that unit tests play in software creation.
Every module should have unit tests, and every application should be made up of modules. In other words, if you’re not writing unit tests, you should be.
Too much configuration : Choose an assertion library, chose a reporting library, chose a task runner (Grunt, Gulp, etc…) Then figure out how to translate the documentation examples to the reporting library / task runner you chose. All of this is too much cognitive load. Vs: Choose Tape. Done.
**Globals: **Mocha, Jasmine, and several other alternatives pollute the global environment with functions like describe, it, etc… Some assertion libraries extend built-in prototypes. Aside from removing the self-documenting nature of simple module exports, those decisions could potentially conflict with the code you’re trying to test. Vs: Tape’s simple module export.
Shared State: Functions like beforeEach and afterEach actively encourage you to do something you definitely should not do: Share state between tests. Vs. Tape: No such functions for global state sharing. Instead, call setup and teardown routines from individual tests, and*contain all state to local test variables.*
Mocha does way too much and gives developers way too many assertion choices, and that leads to analysis paralysis and lost productivity. Every time I have seen Mocha used on a project, I’ve seen developers dump way too much time in the testing framework and testing environment.
While I’m ranting I would be remiss if I didn’t mention that if you spend a lot of time on mocks and stubs, that’s a strong code-smell. You can probably dramatically simplify both your tests and your application by breaking your app into more modular chunks.
A few simple mocks here and there are OK. Some of your app will inevitably involve side-effects (reading from or writing to the network or filesystem, for instance). When you do have a genuine need for mocks, keep them simple. Little more than basic stubs are ideal. But on many projects, I’ve seen a lot of over-complicated mocks that never needed to exist in the first place. Why maintain more code than you need to? The more you break your problems down into simple, pure functions, the easier it will be to test your code without mocks. 一些简单的 Mocking 可以接受。部分情况下,您的某些应用程序不可避免地会被带入副作用(例如,从网络或文件系统读取或写入网络或文件系统)。当您确实需要 Mock 时,请尽量使其简单。采用更多基础 stubs 是不错的主意。但是在许多项目中,我看到了很多过于复杂的 Mock,这些 Mock 从一开始就不需要存在。为什么要维护比您所需更多的代码? 将问题分解成简单的纯函数的次数越多,_无需 Mock 就可以测试代码_。
Testing is not what you should spend most of your time doing.
你不应该投入大量时间去编写测试用例
You should spend most of your time thinking about how to create the best, most flexible, most performant solutions given the afforded time constraints. Time is value in the software development world, and you shouldn’t waste one minute of it.
With many BDD assertion libraries, there are getters with side effects. At one company I worked for (not naming names), that buried a bug in one of our tests, and we spent far too long debugging the test case rather than developing actual application code.
equal, deepEqual, pass & fail are my primary go-to assertions. If equal and deepEqual were the only assertions available anywhere, the testing world would probably be better off for it.
Why? equal & deepEqual provide quality information about expectations, and they lead to very concise test cases that are easy to read & maintain.
When you write a bug report, you should always provide a description, explain what you expected to see, and explain what you actually saw.
Test cases should be written in much the same way:
Describe the feature that you’re testing in plain English.
Provide the expected outcome of the test. This part is why many unit tests are called expectations.
Compare that to the actual value.
When a unit tests fails, the error message is your bug report.
equal, deepEqual, pass & fail 是我常用的断言。如果*equal* and deepEqual 是唯一可用的断言,那么测试可读性会变得更好。
test('A passing test', (assert) => { assert.pass('This test will pass.')
assert.end() })
test('Assertions with tape.', (assert) => { const expected = 'something to test' const actual = 'sonething to test'
assert.equal( actual, expected, 'Given two mismatched values, .equal() should produce a nice bug report' )
assert.end() })
If you write tests this way, your test error messages should be clear enough to use as bug reports:
如果你这么写测试用例,则你的测试错误信息将会足够清楚,甚至用来当作 bug 报告来看。
1 2 3 4 5 6 7 8 9 10 11 12 13
TAP version 13 # A passing test ok 1 This test will pass. # Assertions with tape. not ok 2 Given two mismatched values, .equal() should produce a nice bug report --- operator: equal expected: 'something to test' actual: 'sonething to test' ...1..2 # tests 2 # pass 1 # fail 1
Your automated test error messages are your bug reports.
您的自动测试错误消息就是您的错误报告。
Simple tests assertions provide:
Better readability.
Less code.
Less maintenance.
These features trump all the bells and whistles in the world.