After the test finishes, Sinon not only restores all stubs and mocks, it also conveniently verifies all mocks, meaning that the above test could be written like Listing 16.18... Listing 16
Trang 116.6 Mocks
Mocks have been mentioned many times throughout the book, but never explained
or used The reason is that manually creating mocks is not as easy as manually creat-ing stubs and spies Like stubs, mocks are objects with pre-programmed behavior
Additionally, a mock has pre-programmed expectations and built-in behavior ver-ification Using mocks turns the test upside-down; first we state the expectations, then we exercise the system Finally we verify that all the mock’s expectations were met Listing 16.17 shows an example using with the “start polling” test
Listing 16.17 Mocking ajax.poll
"test connect should start polling": function () { this.client.url = "/my/url";
var mock = sinon.mock(ajax) mock.expects("poll").withArgs("/my/url").returns({});
this.client.connect();
mock.verify();
} This test states its success criteria upfront It does so by creating a mock for the ajaxobject, and adding an expectation on it It expects the poll method to be called exactly once, with the URL as argument In contrast to the stubs we’ve used
so far, mocks fail early If the poll method is called a second time, it immediately throws an ExpectationError, failing the test
16.6.1 Restoring Mocked Methods
The mocks can be undone just like the stubs, by calling restore on the mocked method Additionally, calling verify implicitly restores the mocked method How-ever, if the test throws an exception before the call to verify, we might end up leaking the mock into another test, causing a ripple effect
Sinon’s sandbox feature can mitigate the problem for mocks just as much
as it does for stubs When wrapping the test method in a sinon.test call,
it will receive a mock method as its second parameter, suitable for safe mock-ing After the test finishes, Sinon not only restores all stubs and mocks, it also conveniently verifies all mocks, meaning that the above test could be written like Listing 16.18
Trang 2Listing 16.18 Verifying mocks automatically
"test connect should start polling":
sinon.test(function (stub, mock) {
var url = this.client.url = "/my/url";
mock(ajax).expects("poll").withArgs(url).returns({});
this.client.connect();
})
The mock once again expects exactly one call—no more, no less These three lines replace the original four-line test along with both the setUp and tearDown
methods Less code means less chance of bugs, less code to maintain, and less code
to read and understand However, that alone does not necessarily mean you should
prefer mocks to stubs, or even use fakes at all
16.6.2 Anonymous Mocks
Mocks, like stubs, can be simple anonymous functions to pass into the system
All mocks, including anonymous ones, support the same interface as stubs to
pre-program them to return specific values or throw exceptions Additionally, using
Sinon’s sandbox, they can be automatically verified, allowing for really short and
concise tests
Listing 16.19 revisits the observable test from Listing 16.6, this time using mocks to create anonymous mock functions, one of which is set up to throw an
exception As did the previous mocks, the anonymous mocks expect exactly one
call
Listing 16.19 Using mocks to verify observable’s notify
"test observers should be notified even when some fail":
sinon.test(function(stub, mock) {
var observable = Object.create(tddjs.util.observable);
observable.addObserver(mock().throwsException());
observable.addObserver(mock());
observable.notifyObservers();
})
Because sinon.test keeps record of all stubs and mocks, and automatically verifies mocks, this test does not need local references to the two mock functions
From the Library of WoweBook.Com
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
Trang 316.6.3 Multiple Expectations
Using mocks, we can form complex expectations by expecting several calls, some
or all with differing arguments and this values The expectation returned by expects can be tuned by calling methods such as withArgs as seen above;
withExactArgs, which does not allow excessive arguments; as well as never, once, twice, and the more generic atLeast, atMost, and exactly methods, which tune the number of expected calls
Listing 16.20 shows one of the original Comet client tests, which expects the connectmethod not to be called once the client is connected
Listing 16.20 Expecting connect not to be called a second time
"test should not connect if connected": function () { this.client.url = "/my/url";
ajax.poll = stubFn({});
this.client.connect();
ajax.poll = stubFn({});
this.client.connect();
assertFalse(ajax.poll.called);
} Using Sinon mocks, we can rewrite this test in two ways The default expectation
on mocks is that they will be called one time, and one time only Never calling them, or calling them two times causes an ExpectationError, failing the test
Even though one call is the default expectation, we can make it explicit, as seen in Listing 16.21
Listing 16.21 Explicitly expecting one call
"test should not connect if connected":
sinon.test(function (stub, mock) { this.client.url = "/my/url";
mock(ajax).expects("poll").once().returns({});
this.client.connect();
this.client.connect();
}) Notice how the this value retains its implicit binding to the test case, even as
a callback to sinon.test The second way to write this test using mocks, which mirrors the original test more closely, can be seen in Listing 16.22
Trang 4Listing 16.22 Using the never method
"test should not connect if connected":
sinon.test(function (stub, mock) {
this.client.url = "/my/url";
stub(ajax, "poll").returns({});
this.client.connect();
mock(ajax).expects("poll").never();
this.client.connect();
})
The test looks different, but behaves exactly like the previous one; if the poll method is called a second time, it will immediately throw an exception that fails the
test The only difference between these two tests is the resulting exception message
in case they fail Using once to expect only call will probably yield an error message
closer to the intended result than first stubbing the method and then mocking it
with the never modifier
16.6.4 Expectations on the this Value
Mocks are capable of any kind of inspection possible with test spies In fact, mocks
use test spies internally to record information about calls to them Listing 16.23
shows one of the tests from the chat client’s user form controller It expects the
controller’s handleSubmit method bound to it as the submit event handler
Listing 16.23 Expecting the event handler to be bound to the controller
"test should handle event with bound handleSubmit":
sinon.test(function (stub, mock) {
var controller = this.controller;
stub(dom, "addEventHandler");
mock(controller).expects("handleSubmit").on(controller);
controller.setView(this.element);
dom.addEventHandler.getCall(0).args[2]();
})
This test shows how to use the test spy’s retrieval interface to get the first call to the dom.addEventHandler method, and then accessing its args array, which
contains the received arguments
From the Library of WoweBook.Com
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
Trang 516.7 Mocks or Stubs?
The comparison of stubs and mocks raises the question, stubs or mocks? Unfortu-nately, there is no answer, other than “it depends.” Stubs are more versatile; they can
be used simply to silence dependencies, fill in for not-yet-implemented interfaces, force a certain path through the system, and more Stubs also support both state verification and behavior verification Mocks can be used in most scenarios as well, but only support behavior verification
Although mocks can also be used to silence dependencies, doing so is somewhat unpractical because we must take care to set up the expectations to account for the minimum amount of possible calls, for example by using expectation
atLeast(0) Wrapping tests in sinon.test and using mocks definitely yields the fewest lines of test code When using stubs, assertions are required, something the implicit mock verification deals away with However, as assertions go away, tests may also end up less clear and intent revealing
The upfront expectations used by mocks break the convention that the verifica-tion stage is always carried out last When mocks are involved, we need to scan the entire test for verification code The problem can be mitigated by keeping mock ex-pectations at the top of the test, but there is still a possibility that further verification
is carried out in assertions in the bottom of the test
Although the choice between stubs and mocks is mainly one of personal pref-erence and project convention, there are cases in which you definitely should not use mocks Because mocks implicitly perform behavior verification that can break the test—both during the test and after—mocks should never be casually used to fake interfaces that are not the focus of a given test
As an example of unsuitable use of mocks, consider Listing 16.24, which shows
an excerpt of the chat client’s form controller handleSubmit test case The setUp creates an inline model object whose publish method is a stub Not all tests interact with this object, but it is required by the controller, which is why it’s fed to the controller in the setUp method
Listing 16.24 A stub that should not be made into a mock
setUp: function () { /* */
this.controller = Object.create(messageController);
this.model = { publish: stubFn() };
this.controller.setModel(this.model);
/* */
Trang 6},
"test should prevent event default action": function () {
this.controller.handleSubmit(this.event);
assert(this.event.preventDefault.called);
}
Assuming we fell completely in love with mocks, we might have gone and mocked that model object rather than stubbing it Doing so means that any test may
fail as a result of unexpected interaction with the model object—even the tests that
focus on something entirely different, such as the event object’s preventDefault
method being called Mocks should be treated with the same respect as assertions;
don’t add ones that test things you already know, and don’t add ones that don’t
support the goal of the test
In the case of using a top-down approach to implement, e.g., the user interface before dependencies such as model objects, both mocks and stubs are good choices
In this case tests will have to rely on behavior verification alone in any case, meaning
that stubs lose their advantage of supporting less implementation-specific state
ver-ification In the general sense, however, mocks always rely on behavior verification;
thus, they are inherently more implementation specific
16.8 Summary
In this chapter we have taken a deep dive into the concept of test doubles, focusing
mainly on stubs, spies and, mocks Although we have used stubs and spies frequently
throughout Part III, Real-World Test-Driven Development in JavaScript, looking at
them from a wider angle has allowed us to coin some common usage patterns and
describe them using established terminology
Having gotten through all of five sample projects without one, we investigated the effects of using a stubbing and mocking library in tests The manual approach
is easy to employ in JavaScript, and will take you far Still, using a dedicated library
can reduce the stubbing and mocking related scaffolding, which leads to leaner tests
and less repetition Removing manual stubbing logic in favor of a well tested library
also reduces chances of bugs in tests
In light of Sinon, the stubbing and mocking library, mocks were finally pre-sented Mocks are stubs pre-programmed with expectations that translate into
be-havior verification Mocks fail early, by throwing an exception immediately upon
receiving an unexpected call
From the Library of WoweBook.Com
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
Trang 7Closing off the chapter, we discussed mocks versus stubs, wherein we concluded that stubs are generally more versatile and should be used for isolation purposes that don’t directly support the goal of the test Apart from those cases, the choice between stubs and mocks for behavior verification largely is one of personal preference
In the next, and last chapter, Chapter 17, Writing Good Unit Tests, we will
extract and review some testing patterns and best practices from our previous sample projects
Trang 8This page intentionally left blank
From the Library of WoweBook.Com
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
Trang 917
Writing Good Unit Tests
Unit tests can be an incredible asset When writing tests as part of the test-driven development cycle, tests help form the design of production code, provide us with
an indication of progress, and help us scope down and only implement what we really need When writing tests after the fact, they help form a suite of regression tests and a security net in which we can comfortably refactor code However, simply adding unit tests to a project will not magically fix it Bad tests not only provide little value, they can do actual damage to productivity and the ability to evolve the code base
Writing good tests is a craft Even if you already are a skilled programmer, you will find that getting good at writing tests takes time and practice Throughout the
example projects in Part III, Real-World Test-Driven Development in JavaScript, we
have written a lot of tests, done a fair amount of refactoring, and gotten comfortable with test-driven development In this final chapter we will identify some guidelines for writing quality tests As you practice and improve your tests, you can build on this list, adding your own insights
By the end of this chapter you will be able to better understand some of
the choices we made throughout Part III, Real-World Test-Driven Development
in JavaScript, as well as pinpoint problems that could have been solved in a better
way
Trang 1017.1 Improving Readability
Writing tests that can be trusted, are easy to maintain, and clearly state their intent
takes practice If you have coded along with the examples in Part III, Real-World
Test-Driven Development in JavaScript, you should already have some basic training
doing this, and possibly even have started to develop a nose for good tests
Readability is a key aspect of a good unit test If a test is hard to read it is likely
to be misunderstood This can lead to unfortunate modifications of either tests or
production code, causing the quality of both to drop over time A good test suite
effectively documents the code under test, and provides a simple overview of what
the code can be expected to do and how it can be used
17.1.1 Name Tests Clearly to Reveal Intent
The name of a test should clearly and unambiguously state what the purpose of the
test is A good name makes it easier to understand what a test is trying to achieve, thus
it has more value as unit level documentation and it lessens the chance of someone
changing the test without properly understanding what it’s supposed to verify A
good name also shows up in the test runner’s report when it fails, pinpointing the
exact source of error
When working with TDD, the test name is the very first time you put a feature down in code Writing the requirement out in words may help us mentally prepare
for the feature we are about to add If you find it hard to clearly state what the test
is supposed to do, then it is likely you have not properly recognized the goal of the
test, and it is unlikely that jumping straight to writing test code will result in any
kind of quality unit test, or production code for that matter
17.1.1.1 Focus on Scannability
Good test names make test cases easy to scan Scanning a test case with well-named
tests should give us a good high-level understanding of what the module being tested
does and how it is expected to behave in response to given input It can also help
us understand what kinds of cases are not accounted for, which can be useful when
encountering trouble using a library in a specific way
Although naming is one of those things in which personal preference does have
a play in what is “clear,” I’ve found the following rules of thumb to be of good help
• JavaScript property identifiers can be arbitrary strings Use this powerful feature to name tests with short sentences using spaces, no underscores or camelCasedTestNames
• Using the word “should” underpins the test as a behavior specification
From the Library of WoweBook.Com
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.