1. Trang chủ
  2. » Công Nghệ Thông Tin

Test Driven JavaScript Development- P14 pdf

20 291 0
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 20
Dung lượng 193,36 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Rather than creating an actual object, Listing 12.15 is going to stub out the ajax.create method, make a call to ajax.get, and then assert that ajax.create was called.. Listing 12.15 Man

Trang 1

Microsoft.XMLHTTPwill do, as Msxml2.XMLHTTP.3.0 (again, ships with IE6) includes the Microsoft.XMLHTTP alias for backwards compatibility

12.3.3 Implementing tddjs.ajax.create

With knowledge of the different objects available, we can take a shot at implementing ajax.create, as seen in Listing 12.6

Listing 12.6 Creating an XMLHttpRequest object

tddjs.namespace("ajax").create = function () { var options = [

function () { return new ActiveXObject("Microsoft.XMLHTTP");

}, function () { return new XMLHttpRequest();

} ];

for (var i = 0, l = options.length; i < l; i++) { try {

return options[i]();

} catch (e) {}

} return null;

};

Running the tests confirms that our implementation is sufficient First test green!

Before we hasten on to the next test, we should look for possible duplication and other areas that could be improved through refactoring Although there is no obvi-ous duplication in code, there is already duplication in execution—the try/catch to find a suitable object is executed every time an object is created This is wasteful, and we can improve the method by figuring out which object is available before defining it This has two benefits: The call time overhead is eliminated, and fea-ture detection becomes built-in If there is no matching object to create, then there will be no tddjs.ajax.create, which means that client code can simply test for its existence to determine if XMLHttpRequest is supported by the browser

Listing 12.7 improves the method

Trang 2

Listing 12.7 Checking for support upfront

(function () {

var xhr;

var ajax = tddjs.namespace("ajax");

var options = [/* */]; // Same as before for (var i = 0, l = options.length; i < l; i++) { try {

xhr = options[i]();

ajax.create = options[i];

break;

} catch (e) {}

} }());

With this implementation in place, the try/catch will only run at load time If successfully created, ajax.create will call the correct function directly The test

still runs green, so we can focus on the next requirement

12.3.4 Stronger Feature Detection

The test we just wrote is bound to work as long as it is run with the basic JsTestDriver

setup (seeing as JsTestDriver requires the XMLHttpRequest object or equivalent)

However, the checks we did in Listing 12.3 are really feature tests that verify the

capa-bilities of the returned object Because we have a mechanism for verifying the object

only once, it would be nice to make the verification as strong as possible For this

reason, Listing 12.8 performs the same tests in the initial execution, making us more

confident that a usable object is returned It requires the tddjs.isHostMethod

method from Chapter 10, Feature Detection, in lib/tdd.js.

Listing 12.8 Adding stronger feature detection

/* */

try {

xhr = options[i]();

if (typeof xhr.readyState == "number" &&

tddjs.isHostMethod(xhr, "open") &&

tddjs.isHostMethod(xhr, "send") &&

tddjs.isHostMethod(xhr, "setRequestHeader")) { ajax.create = options[i];

break;

} } catch (e) {}

Trang 3

12.4 Making Get Requests

We will start working on the request API by describing our ultimate goal: a simple interface to make requests to the server using a URL, an HTTP verb, and possi-bly success and failure callbacks We’ll start with the GET request, as shown in Listing 12.9; save it in test/request_test.js

Listing 12.9 Test for tddjs.ajax.get

TestCase("GetRequestTest", {

"test should define get method": function () { assertFunction(tddjs.ajax.get);

} });

Taking baby steps, we start by checking for the existence of the get method

As expected, it fails because the method does not exist Listing 12.10 defines the method Save it in src/request.js

Listing 12.10 Defining tddjs.ajax.get

tddjs.namespace("ajax").get = function () {};

12.4.1 Requiring a URL

The get method needs to accept a URL In fact, it needs to require a URL

Listing 12.11 has the scoop

Listing 12.11 Testing for a required URL

"test should throw error without url": function () { assertException(function () {

tddjs.ajax.get();

}, "TypeError");

}

Our code does not yet throw any exceptions at all, so we expect this method to fail because of it Luckily it does, so we move on to Listing 12.12

Listing 12.12 Throwing exception if URL is not a string

tddjs.namespace("ajax").get = function (url) {

if (typeof url != "string") { throw new TypeError("URL should be string");

} };

Trang 4

Tests pass Now, is there any duplication to remove? That full namespace is al-ready starting to stick out as slightly annoying By wrapping the test in an anonymous

closure, we can “import” the ajax namespace into the local scope by assigning it

to a variable It’ll save us four keystrokes for each reference, so we go for it, as seen

in Listing 12.13

Listing 12.13 “Importing” the ajax namespace in the test

(function () {

var ajax = tddjs.ajax;

TestCase("GetRequestTest", {

"test should define get method": function () { assertFunction(ajax.get);

},

"test should throw error without url": function () { assertException(function () {

ajax.get();

}, "TypeError");

} });

}());

We can apply the same trick to the source file as well While we’re at it, we can utilize the scope gained by the anonymous closure to use a named function as

well, as seen in Listing 12.14 The function declaration avoids troublesome Internet

Explorer behavior with named function expressions, as explained in Chapter 5,

Functions.

Listing 12.14 “Importing” the ajax namespace in the source

(function () {

var ajax = tddjs.namespace("ajax");

function get(url) {

if (typeof url != "string") { throw new TypeError("URL should be string");

} } ajax.get = get;

}());

Trang 5

12.4.2 Stubbing the XMLHttpRequest Object

In order for the get method to do anything at all, it needs to create an XML-HttpRequestobject We simply expect it to create one using ajax.create

Note that this does introduce a somewhat tight coupling between the request API and the create API A better idea would probably be to inject the transport object

However, we will keep things simple for now Later when we see the big picture clearer, we can always refactor to improve

In order to verify that an object is created, or rather, that a method is called,

we need to somehow fake the original implementation Stubbing and mocking are

two ways to create objects that mimic real objects in tests Along with fakes and

dummies, they are often collectively referred to as test doubles.

12.4.2.1 Manual Stubbing

Test doubles are usually introduced in tests either when original implementations are awkward to use or when we need to isolate an interface from its dependencies In the case of XMLHttpRequest, we want to avoid the real thing for both reasons Rather

than creating an actual object, Listing 12.15 is going to stub out the ajax.create

method, make a call to ajax.get, and then assert that ajax.create was called

Listing 12.15 Manually stubbing the create method

"test should obtain an XMLHttpRequest object": function () { var originalCreate = ajax.create;

ajax.create = function () { ajax.create.called = true;

};

ajax.get("/url");

assert(ajax.create.called);

ajax.create = originalCreate;

}

The test stores a reference to the original method and overwrites it with a func-tion that, when called, sets a flag that the test can assert on Finally, the original method is restored There are a couple of problems with this solution First of all, if

this test fails, the original method will not be restored Asserts throw an

Assert-Errorexception when they fail, meaning that the last line won’t be executed unless

Trang 6

the test succeeds To fix this we can move the reference and restoring of the original

method to the setUp and tearDown methods respectively Listing 12.16 shows

the updated test case

Listing 12.16 Stubbing and restoring ajax.create safely

TestCase("GetRequestTest", {

setUp: function () { this.ajaxCreate = ajax.create;

}, tearDown: function () { ajax.create = this.ajaxCreate;

}, /* */

"test should obtain an XMLHttpRequest object":

function () { ajax.create = function () { ajax.create.called = true;

};

ajax.get("/url");

assert(ajax.create.called);

} });

Before we fix the next problem, we need to implement the method in question

All we have to do is add a single line inside ajax.get, as in Listing 12.17

Listing 12.17 Creating the object

function get(url) {

/* */

var transport = tddjs.ajax.create();

}

With this single line in place the tests go green again

12.4.2.2 Automating Stubbing

The next issue with the stubbing solution is that it’s fairly verbose We can mitigate

this by extracting a helper method that creates a function that sets a flag when called,

Trang 7

and allows access to this flag Listing 12.18 shows one such possible method Save

it in lib/stub.js

Listing 12.18 Extracting a function stubbing helper

function stubFn() { var fn = function () { fn.called = true;

};

fn.called = false;

return fn;

}

Listing 12.19 shows the updated test

Listing 12.19 Using the stub helper

"test should obtain an XMLHttpRequest object": function () { ajax.create = stubFn();

ajax.get("/url");

assert(ajax.create.called);

}

Now that we know that ajax.get obtains an XMLHttpRequest object we need to make sure it uses it correctly The first thing it should do is call its open method This means that the stub helper needs to be able to return an object

Listing 12.20 shows the updated helper and the new test expecting open to be called with the right arguments

Listing 12.20 Test that the open method is used correctly

function stubFn(returnValue) { var fn = function () {

fn.called = true;

return returnValue;

};

fn.called = false;

return fn;

}

Trang 8

TestCase("GetRequestTest", {

/* */

"test should call open with method, url, async flag":

function () { var actual;

ajax.create = stubFn({

open: function () { actual = arguments;

} });

var url = "/url";

ajax.get(url);

assertEquals(["GET", url, true], actual);

} });

We expect this test to fail because the open method isn’t currently being called from our implementation, implying that actual should be undefined This is

exactly what happens and so we can write the implementation, as in Listing 12.21

Listing 12.21 Calling open

function get(url) {

/* */

transport.open("GET", url, true);

}

Now a few interesting things happen First, we hardcoded both the HTTP verb and the asynchronous flag Remember, one step at a time; we can make those

configurable later Running the tests shows that whereas the current test succeeds,

the previous test now fails It fails because the stub in that test did not return an

object, so our production code is attempting to call undefined.open, which

obviously won’t work

The second test uses the stubFn function to create one stub, while manually creating a stub open method in order to inspect its received arguments To fix these

problems, we will improve stubFn and share the fake XMLHttpRequest object

between tests

Trang 9

12.4.2.3 Improved Stubbing

To kill the manual stub open method, Listing 12.22 improves the stubFn func-tion by having it record the arguments it receives and making them available for verification in tests

Listing 12.22 Improving the stub helper

function stubFn(returnValue) { var fn = function () {

fn.called = true;

fn.args = arguments;

return returnValue;

};

fn.called = false;

return fn;

}

Using the improved stubFn cleans up the second test considerably, as seen in Listing 12.23

Listing 12.23 Using the improved stub function

"test should call open with method, url, async flag":

function () { var openStub = stubFn();

ajax.create = stubFn({ open: openStub });

var url = "/url";

ajax.get(url);

assertEquals(["GET", url, true], openStub.args);

}

We now generate a stub for ajax.create that is instructed to return an object with one property: a stubbed open method To verify the test we assert that openwas called with the correct arguments

The second problem was that adding the call to transport.open caused the first test, which didn’t return an object from the stubbed ajax.create method, to fail To fix this we will extract a fake XMLHttpRequest object, which can be shared between tests by stubbing ajax.create to return it The stub can be conveniently created in the test case’s setUp We will start with the fakeXMLHttpRequest object, which can be seen in Listing 12.24 Save it in lib/fake_xhr.js

Trang 10

Listing 12.24 Extracting fakeXMLHttpRequest

var fakeXMLHttpRequest = {

open: stubFn() };

Because the fake object relies on stubFn, which is defined in lib/stub.js,

we need to update jsTestDriver.conf to make sure the helper is loaded before

the fake object Listing 12.25 shows the updated configuration file

Listing 12.25 Updating jsTestDriver.conf to load files in correct order

server: http://localhost:4224

load:

- lib/stub.js

- lib/*.js

- src/*.js

- test/*.js

Next up, we update the test case by elevating the ajax.create stub to setUp To create the fakeXMLHttpRequest object we will use Object

create from Chapter 7, Objects and Prototypal Inheritance, so place this

func-tion in lib/object.js Listing 12.26 shows the updated test case

Listing 12.26 Automate stubbing of ajax.create and XMLHttpRequest

TestCase("GetRequestTest", {

setUp: function () { this.ajaxCreate = ajax.create;

this.xhr = Object.create(fakeXMLHttpRequest);

ajax.create = stubFn(this.xhr);

}, /* */

"test should obtain an XMLHttpRequest object":

function () { ajax.get("/url");

assert(ajax.create.called);

},

"test should call open with method, url, async flag":

function () {

Trang 11

var url = "/url";

ajax.get(url);

assertEquals(["GET", url, true], this.xhr.open.args);

} });

Much better Re-running the tests confirm that they now all pass Moving for-ward, we can add stubs to the fakeXMLHttpRequest object as we see fit, which will make testing ajax.get significantly simpler

12.4.2.4 Feature Detection and ajax.create

ajax.getnow relies on the ajax.create method, which is not available in the case that the browser does not support the XMLHttpRequest object To make sure

we don’t provide an ajax.get method that has no way of retrieving a transport,

we will define this method conditionally as well Listing 12.27 shows the required test

Listing 12.27 Bailing out if ajax.create is not available

(function () { var ajax = tddjs.namespace("ajax");

if (!ajax.create) { return;

} function get(url) { /* */

} ajax.get = get;

}());

With this test in place, clients using the ajax.get method can add a similar test to check for its existence before using it Layering feature detection this way makes it manageable to decide what features are available in a given environment

12.4.3 Handling State Changes

Next up, the XMLHttpRequest object needs to have its onreadystatechange handler set to a function, as Listing 12.28 shows

Ngày đăng: 03/07/2014, 05:20

TỪ KHÓA LIÊN QUAN