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

Phát triển Javascript - part 36 pps

10 182 0

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

THÔNG TIN TÀI LIỆU

Nội dung

ptg 13.4 The Comet Client 323 13.4.2 Introducing ajax.cometClient As usual we’ll start out real simple, asserting that the object in question exists. ajax.cometClient seems like a reasonable name, and Listing 13.43 tests for its existence. The test lives in the new file test/comet _ client _ test.js. Listing 13.43 Expecting ajax.cometClient to exist (function () { var ajax = tddjs.ajax; TestCase("CometClientTest", { "test should be object": function () { assertObject(ajax.cometClient); } }); }()); Implementation is a matter of initial file setup as per usual, seen in Listing 13.44. Listing 13.44 Setting up the comet _ client.js file (function () { var ajax = tddjs.namespace("ajax"); ajax.cometClient = {}; }()); 13.4.3 Dispatching Data When an observer is added, we expect it to be called when data is dispatched from the client. Although we could write tests to dictate the internals of the observe method, those would be needlessly implementation specific, without describing the expected behavior very well. Besides, we are going to use the observable object to handle observers and we don’t want to replicate the entire observable test case for the client’s observe method. We will start by implementing dispatch, which later can help us verify the behavior of observe. Dispatching is the act of breaking up data received from the server and sending it out to observers. From the Library of WoweBook.Com Download from www.eBookTM.com ptg 324 Streaming Data with Ajax and Comet 13.4.3.1 Adding ajax.cometClient.dispatch The first test for dispatching data is simply asserting that a method exists, as Listing 13.45 shows. Listing 13.45 Expecting dispatch to exist "test should have dispatch method": function () { var client = Object.create(ajax.cometClient); assertFunction(client.dispatch); } This test fails, so Listing 13.46 adds it in. Listing 13.46 Adding the dispatch method function dispatch() { } ajax.cometClient = { dispatch: dispatch }; 13.4.3.2 Delegating Data Next, we’re going to feed dispatch an object, and make sure it pushes data out to observers. However, we haven’t written observe yet, which means that if we now write a test that requires both methods to work correctly, we’re in trouble if either fail. Failing unit tests should give a clear indication of where a problem occurred, and using two methods to verify each other’s behavior is not a good idea when none of them exist. Instead, we will leverage the fact that we’re going to use observable to implement both of these. Listing 13.47 expects dispatch to call notify on the observable observers object. Listing 13.47 Expecting dispatch to notify "test dispatch should notify observers": function () { var client = Object.create(ajax.cometClient); client.observers = { notify: stubFn() }; client.dispatch({ someEvent: [{ id: 1234 }] }); var args = client.observers.notify.args; From the Library of WoweBook.Com Download from www.eBookTM.com ptg 13.4 The Comet Client 325 assert(client.observers.notify.called); assertEquals("someEvent", args[0]); assertEquals({ id: 1234 }, args[1]); } The simple data object in this test conforms to the format we specified in the introduction. To pass this test we need to loop the properties of the data object, and then loop each topic’s events and pass them to the observers, one by one. Listing 13.48 takes the job. Listing 13.48 Dispatching data function dispatch(data) { var observers = this.observers; tddjs.each(data, function (topic, events) { for (var i = 0, l = events.length; i < l; i++) { observers.notify(topic, events[i]); } }); } The test passes, but this method clearly makes a fair share of assumptions; thus, it can easily break in lots of situations. We’ll harden the implementation through a series of small tests for discrepancies. 13.4.3.3 Improved Error Handling Listing 13.49 asserts that it doesn’t break if there are no observers. Listing 13.49 What happens if there are no observers? TestCase("CometClientDispatchTest", { setUp: function () { this.client = Object.create(ajax.cometClient); }, /* */ "test should not throw if no observers": function () { this.client.observers = null; assertNoException(function () { this.client.dispatch({ someEvent: [{}] }); }.bind(this)); }, From the Library of WoweBook.Com Download from www.eBookTM.com ptg 326 Streaming Data with Ajax and Comet "test should not throw if notify undefined": function () { this.client.observers = {}; assertNoException(function () { this.client.dispatch({ someEvent: [{}] }); }.bind(this)); } }); All the dispatch tests are now grouped inside their own test case. The test case adds two new tests: one that checks that dispatch can deal with the case in which there is no observers object, and another in which the observers object has been tampered with. The latter test is there simply because the object is public and could possibly be mangled. Both tests fail, so Listing 13.50 hardens the implementation. Listing 13.50 Being careful with observers function dispatch(data) { var observers = this.observers; if (!observers || typeof observers.notify != "function") { return; } /* */ } Next up, we go a little easier on the assumptions on the data structure the method receives. Listing 13.51 adds two tests that tries (successfully, for now) to overthrow dispatch by feeding it bad data. Listing 13.51 Testing dispatch with bad data TestCase("CometClientDispatchTest", { setUp: function () { this.client = Object.create(ajax.cometClient); this.client.observers = { notify: stubFn() }; }, /* */ "test should not throw if data is not provided": function () { From the Library of WoweBook.Com Download from www.eBookTM.com ptg 13.4 The Comet Client 327 assertNoException(function () { this.client.dispatch(); }.bind(this)); }, "test should not throw if event is null": function () { assertNoException(function () { this.client.dispatch({ myEvent: null }); }.bind(this)); } }); Running the tests somewhat surprisingly reveals that only the last test fails. The tddjs.each method that is used for looping was built to handle input not suitable for looping, so dispatch can already handle null and a missing data argument. To pass the last test, we need to be a little more careful in the loop over event objects, as seen in Listing 13.52. Listing 13.52 Carefully looping event data function dispatch(data) { /* */ tddjs.each(data, function (topic, events) { var length = events && events.length; for (var i = 0; i < length; i++) { observers.notify(topic, events[i]); } }); } In order to make the dispatch test case complete, we should add some tests that make sure that notify is really called for all topics in data, and that all events are passed to observers of a topic. I’ll leave doing so as an exercise. 13.4.4 Adding Observers With a functional dispatch we have what we need to test the observe method. Listing 13.53 shows a simple test that expects that observers to be called when data is available. From the Library of WoweBook.Com Download from www.eBookTM.com ptg 328 Streaming Data with Ajax and Comet Listing 13.53 Testing observers TestCase("CometClientObserveTest", { setUp: function () { this.client = Object.create(ajax.cometClient); }, "test should remember observers": function () { var observers = [stubFn(), stubFn()]; this.client.observe("myEvent", observers[0]); this.client.observe("myEvent", observers[1]); var data = { myEvent: [{}] }; this.client.dispatch(data); assert(observers[0].called); assertSame(data.myEvent[0], observers[0].args[0]); assert(observers[1].called); assertSame(data.myEvent[0], observers[1].args[0]); } }); observe is still an empty method, so this test fails. Listing 13.54 pieces in the missing link. For this to work you need to save the observable implementation from Chapter 11, The Observer Pattern, in lib/observable.js. Listing 13.54 Remembering observers (function () { var ajax = tddjs.ajax; var util = tddjs.util; /* */ function observe(topic, observer) { if (!this.observers) { this.observers = Object.create(util.observable); } this.observers.observe(topic, observer); } ajax.cometClient = { dispatch: dispatch, observe: observe }; }); From the Library of WoweBook.Com Download from www.eBookTM.com ptg 13.4 The Comet Client 329 The tests now all pass. The observe method could probably benefit from type checking this.observers.observe like we did with notify in dispatch. You might also have noticed that there are no tests asserting what happens if either topic or events is not what we expect it to be. I urge you to cover those paths as an exercise. Both topic and observer are actually checked for us by observable. observe, but relying on it ties the client more tightly to its dependencies. Be- sides, it’s generally not considered best practice to allow exceptions to bubble a long way through a library, because it yields stack traces that are hard to debug for a developer using our code. 13.4.5 Server Connection So far, all we have really done is to wrap an observable for a given data format. It’s time to move on to connecting to the server and making it pass response data to the dispatch method. The first thing we need to do is to obtain a connection, as Listing 13.55 specifies. Listing 13.55 Expecting connect to obtain a connection TestCase("CometClientConnectTest", { setUp: function () { this.client = Object.create(ajax.cometClient); this.ajaxPoll = ajax.poll; }, tearDown: function () { ajax.poll = this.ajaxPoll; }, "test connect should start polling": function () { this.client.url = "/my/url"; ajax.poll = stubFn({}); this.client.connect(); assert(ajax.poll.called); assertEquals("/my/url", ajax.poll.args[0]); } }); From the Library of WoweBook.Com Download from www.eBookTM.com ptg 330 Streaming Data with Ajax and Comet In this test we no longer use the fake XMLHttpRequest object, because the semantics of ajax.poll better describes the expected behavior. Asserting that the method started polling in terms of fakeXMLHttpRequest would basically mean duplicating ajax.poll’s test case. The test fails because connect is not a method. We will add it along with the call to ajax.poll in one go, as seen in Listing 13.56. Listing 13.56 Connecting by calling ajax.poll (function () { /* */ function connect() { ajax.poll(this.url); } ajax.cometClient = { connect: connect, dispatch: dispatch, observe: observe } }); What happens if we call connect when the client is already connected? From the looks of things, more polling. Listing 13.57 asserts that only one connection is made. Listing 13.57 Verifying that ajax.poll is only called once "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); } To pass this test we need to keep a reference to the poller, and only connect if this reference does not exist, as Listing 13.58 shows. From the Library of WoweBook.Com Download from www.eBookTM.com ptg 13.4 The Comet Client 331 Listing 13.58 Only connect once function connect() { if (!this.poller) { this.poller = ajax.poll(this.url); } } Listing 13.59 tests for a missing url property. Listing 13.59 Expecting missing URL to cause an exception "test connect should throw error if no url exists": function () { var client = Object.create(ajax.cometClient); ajax.poll = stubFn({}); assertException(function () { client.connect(); }, "TypeError"); } Passing this test is three lines of code away, as seen in Listing 13.60. Listing 13.60 Throwing an exception if there is no URL function connect() { if (!this.url) { throw new TypeError("client url is null"); } if (!this.poller) { this.poller = ajax.poll(this.url); } } The final missing piece is the success handler that should call dispatch with the returned data. The resulting data will be a string of JSON data, which needs to be passed to dispatch as an object. To test this we will use the fakeXML- HttpRequest object once again, to simulate a completed request that returns with some JSON data. Listing 13.61 updates the fakeXMLHttpRequest.complete method to accept an optional response text argument. From the Library of WoweBook.Com Download from www.eBookTM.com ptg 332 Streaming Data with Ajax and Comet Listing 13.61 Accepting response data in complete var fakeXMLHttpRequest = { /* */ complete: function (status, responseText) { this.status = status || 200; this.responseText = responseText; this.readyStateChange(4); } } Listing 13.62 shows the test, which uses the updated complete method. Listing 13.62 Expecting client to dispatch data TestCase("CometClientConnectTest", { setUp: function () { /* */ this.ajaxCreate = ajax.create; this.xhr = Object.create(fakeXMLHttpRequest); ajax.create = stubFn(this.xhr); }, tearDown: function () { /* */ ajax.create = this.ajaxCreate; }, /* */ "test should dispatch data from request": function () { var data = { topic: [{ id: "1234" }], otherTopic: [{ name: "Me" }] }; this.client.url = "/my/url"; this.client.dispatch = stubFn(); this.client.connect(); this.xhr.complete(200, JSON.stringify(data)); assert(this.client.dispatch.called); assertEquals(data, this.client.dispatch.args[0]); } }); From the Library of WoweBook.Com Download from www.eBookTM.com . us by observable. observe, but relying on it ties the client more tightly to its dependencies. Be- sides, it’s generally not considered best practice to allow exceptions to bubble a long way through. JSON data, which needs to be passed to dispatch as an object. To test this we will use the fakeXML- HttpRequest object once again, to simulate a completed request that returns with some JSON data.

Ngày đăng: 04/07/2014, 22:20