CHAPTER 21
Testing Advanced Program Architectures

WHAT’S IN THIS CHAPTER?            

WROX.COM CODE DOWNLOADS FOR THIS CHAPTER

The wrox.com code downloads for this chapter are found at www.wrox.com/go/reliablejavascript on the Download Code tab. The files are in the Chapter 21 download and individually named according to the filenames noted throughout this chapter.

Most programs you have written were probably written to achieve some goal. The architecture may have been the conventional, top-down variety that you learned along with object-oriented programming. The architectures you will consider in this chapter are different. They allow independent parts to interact in a decentralized manner. The overall goal happens as if by magic.

The patterns in question are known as the Observer and Mediator. They are similar in that they are decentralized, but the means of communication differ.

ENSURING RELIABLE USE OF THE OBSERVER PATTERN

If you’ve subscribed to daily newspaper delivery, you’ve participated in a real-life implementation of the Observer Pattern. By sending your subscription card to the newspaper publisher, you alerted the publisher that you would like to have the paper delivered to your home every day.

Once your address is on the publisher’s list, there’s nothing more that you need to do. When the paper is ready each day, the publisher dispatches a copy to your address and each of the other addresses in its list.

If you decide to discontinue daily delivery, all you must do is ask the publisher to remove your address from its list of addresses. The list of addresses the publisher maintains regularly grows and shrinks as new subscriptions are added and existing subscriptions are cancelled.

An important aspect of your relationship with the publisher is that all the newspaper publisher knows about you, or any of its other subscribers, is the address to which the paper should be delivered. The publisher doesn’t know if your home is blue, if it’s made of brick, or if it has three floors. The delivery of the newspaper doesn’t rely on any of those aspects of your home. You can change your home’s color or add a floor, and the paper will continue to show up.

If you describe the newspaper subscription example with the terminology of the intent of the pattern, as described by Design Patterns, you find the following:

One of the goals of developers of reliable software is to minimize the number and scope of dependencies between objects. You may have noticed that intent of the pattern as described by Design Patterns uses a derivative of the word “dependency” not once, but thrice. Does that mean it should be avoided? Definitely not; it should be embraced. You can depend on it to help you write loosely coupled, reliable code.

In fact, one of the most appealing characteristics of the Observer Pattern is that the dependency between the subject, or object that changes state, and its observers, the dependents that are notified of the subject’s state change, is well defined and narrow.

In this section, we show you how use of the Observer Pattern leads to loosely coupled code and also provide techniques for enhancing the reliability of implementations of the pattern.

Examining the Observer Pattern

The Observer Pattern is made up of two pieces:

  • The object being observed (the subject)
  • The objects observing the subject (the observers)

In order to support observation, the subject must provide:

  • The capability for an observer to register itself so that it receives notifications
  • The capability for an observer to unregister itself so that it stops receiving notifications
  • The capability for the subject to send updates to its observers

Additionally, the observer must provide a method the subject can call to alert the observer to a change.

Suppose that the organizers of the JavaScript conference have requested the capability to see information about attendee registrations. They’d like the capability to display the data in two ways:

  • A counter that displays the total number of registrants
  • A list of the names of the ten most recent registrants

As an additional wrinkle, the organizers would like the information to update in near real-time without requiring a browser refresh.

You decide that it would be appropriate to create two modules: one that displays the count and one that displays the names of the recent registrants. The module that displays the count could periodically poll the server to find out if there are any new registrants, and the module that displays the names could also periodically poll the server to find out if there are any new registrants.

“You could write a separate module that’s responsible for polling for new registrants, and it can update the two modules responsible for displaying the information to the user,” Charlotte suggests (can she read your mind?). “The polling module could allow the other modules to register for updates; it would be the subject in the Observer Pattern. The display modules would be the observers.”

Charlotte’s suggestion is appealing for many reasons. First, it nicely separates the concerns of retrieving updates from the server and displaying results to the user. Second, while there is a dependency between the display modules and the data retrieval module, it’s very narrow; it’s only the method on the observer that the subject will call to alert the observer to a change.

Your first task is to create the module that will act as the subject in this implementation of the Observer Pattern: the recentRegistrationsService. This module is responsible for periodically polling the server and retrieving the attendees that have registered since the last time it polled. Also, because it’s participating in the Observer Pattern as a subject, the recentRegistrationsService must also provide the ability for observers to subscribe and unsubscribe, and the ability to notify each of the observers when a new attendee has registered.

The implementation of the recentRegistrationsService follows in Listing 21-1.

The recentRegistrationsService module provides the public methods shown in Table 21.1 that allow it to act as the subject in the Observer Pattern.

Table 21.1 Table 21-1: Methods Allowing recentRegistrationsService as the Subject

METHOD DESCRIPTION
addObserver(observer) Adds observer to the list of observers that will receive an update each time an attendee registers
removeObserver(observer) Removes observer from the list of observers so that it will no longer receive an update each time an attendee registers
updateObservers(newAttendee) Executes each observer’s update method, providing newAttendee as an argument

Along with the methods that are required to act as a subject, the recentRegistrationsService also provides some additional public methods, shown in Table 21.2, that aren’t strictly required in order to participate in the Observer Pattern.

Table 21.2 Table 21-2: Convenience Methods of recentRegistrationsService

METHOD DESCRIPTION
clearObservers() Removes all observers from the list of observers
hasObserver(observer) Returns true if observer has been registered for updates from recentRegistrationsService, false otherwise
stopPolling() Clears the interval set up to poll the server

In addition to exposing the public methods described in Tables 21.1 and 21.2, the recentRegistationsService module has a private method, getNewAttendees, which is responsible for communicating with the server and retrieving the most recent registrants.

Just prior to returning the service instance, service, to the caller, the following is executed:

pollingProcess = setInterval(function pollForNewAttendees(){
  getNewAttendees().then(function processNewAttendees(newAttendees){
    newAttendees.forEach(function updateWithNewAttendee(newAttendee){
      service.updateObservers(newAttendee);
    });
  });
}, 15000);

The previous snippet of code sets up a recurring operation using setInterval to execute getNewAttendees every 15 seconds. The ID of the interval is retained in pollingProcess so that the polling may be canceled. The promise returned by getNewAttendees will resolve to an array with no elements if no attendees have registered in the last 15 seconds. Or, if registrations have occurred, the array will have one or more elements, each representing a newly registered attendee. For each new registration, service’s update method is executed with the new attendee provided as an argument.

Figure 21.1 shows that the tests all pass.

images

Figure 21.1  

With a functional recentRegistrationsService, the subject in this implementation of the Observer Pattern, the next step is to create the modules that will display the total registration count and the most recent ten registrations. These act as the observers in this implementation of the Observer Pattern.

To reiterate, the only thing that an observer must provide to participate in the pattern is a function that the subject can execute when there’s a change that the subject needs to notify the observer about. The recentRegistrationsService module expects that its observer will expose a function called update that accepts an attendee object. As such, the totalAttendeeCount and mostRecentRegistrations modules will each need to implement the update function.

Listing 21-2 provides the unit tests for the functionality of the totalAttendeeCount module related to its role as an observer.

The three tests in Listing 21-2 verify that

  • The instance of totalAttendeeCount registers itself as an observer of the injected instance of recentRegistrationsService.
  • The getCount method returns the initial count value provided to the module function.
  • Executing the update method causes the value returned by getCount to be incremented by one.

It’s important to ensure that the instance registers itself as an observer of recentRegistrationsService because that action is required in order for the module to provide any functionality. Also, if the update method doesn’t increment the count of attendees, the conference organizers may mistakenly think that no one is registering for the conference.

Listing 21-3 provides an implementation of totalAttendeeCount that allows the unit tests to pass.

One thing that you should take away from Listing 21-3 is that there’s very little code required to allow the totalAttendeeCount module to be an observer. The totalAttendeeCount module only needs to provide an update function that accepts an attendee and add itself as an observer of the recentRegistrationsService instance.

Figure 21.2 shows that the implementation of totalAttendeeCount in Listing 21-3 allows the unit tests from Listing 21-2 to pass.

images

Figure 21.2  

Now that both recentRegistrationsService and totalAttendeeCount have passing unit test suites, we’ve maxed out their reliability, right? Well no, not quite. The next section examines what more can be done to help make this implementation of the Observer Pattern defect resistant.

Enhancing the Reliability of the Observer Pattern

Consider the addObserver function from the recentRegistrationsService:

addObserver: function addObserver(observer){
  return registeredObservers.push(observer);
}

What happens if observer doesn’t implement the update function that’s required for it to participate in the Observer Pattern? It’s happily added to the list of registered users, that’s what. When it’s time for recentRegistrationsService to update all of its observers, however, it will encounter the invalid observer and generate an Error. That doesn’t sound very reliable, does it?

Adding insult to injury, debugging the problem is difficult because the error doesn’t occur until the first update is attempted. It would be far more helpful if the error occurred when the invalid observer is added.

The problem could be alleviated by adding a check to addObserver that ensures observer has an update function. While that would address the issue, it isn’t reusable; any other implementations of addObserver would need to perform the same check.

Instead, we’d rather leverage the ContractRegistry from Chapter 17 to apply the verification in a declarative manner. That way, any future objects that act as the subject of the Observer Pattern may utilize the verification as well.

Listing 21-4 shows a unit test that ensures an error is generated when callers provide an object that doesn’t implement an update function as the observer argument to addObserver.

As you might have predicted, the test is extremely simple. It provides an object without an update function to addObserver, and ensures that an error is thrown. Figure 21.3 shows that, while simple, the test doesn’t pass.

images

Figure 21.3  

Implementing the check using the ContractRegistry and contract modules from Chapter 17 is almost as simple. First, a contract module that provides the evaluator function, observerContracts, must be created. It appears in Listing 21-5.

The getContracts function of the observerContracts module returns a single contract definition. The contract definition returned has a name of “observer” and an evaluator that ensures that thing.update is a function. The observerContracts module doesn’t attach any validators of its own.

Along with observerContracts, a contract module that attaches an argument validator to the addObserver function of the recentRegistrationsService is required. That contract module, recentRegistrationsServiceContracts, appears in Listing 21-6.

The meat of Listing 21-6 appears in the attachValidators function. In attachValiadators, the “observer” validator defined in the observerContracts module is applied via aspect to the addObserver function of each recentRegistrationsService instance created.

The ConferenceContractRegistry, which appears in Listing 21-7, defines the contract from the observerContracts module and attaches the validator from the recentRegistrationsServiceContracts module.

The result is that the observer validator is applied to the addObserver function of the recentRegistrationsService, and the new unit test passes. Figure 21.4 shows that all of the recentRegistrationsService tests pass.

images

Figure 21.4  

The end result is that, as long as the ConferenceContractRegistry is included on the conference’s website (or along with the unit tests), you can be sure that an error will be generated whenever addObserver is invoked with an argument that cannot act as an observer.

In a manner similar to that which was used to ensure addObserver is invoked with a proper observer object, the recentRegistrationsServiceContracts module may be extended to validate that the argument provided to the updateObservers function satisfies the Conference.attendee .personalInfo contract that was created in Chapter 17. Likewise, the same may be done to ensure that the appropriate argument is provided to the totalAttendeeCount module’s update function.

ENSURING RELIABLE USE OF THE MEDIATOR PATTERN

For over 50 years, the United States and Cuba had no official relationship. The longer the parties didn’t talk, the less likely it seemed they ever would or could. Then, in 2014, Pope Francis wrote to Presidents Obama and Castro, appealing to them to “resolve humanitarian questions of common interest.” Both countries responded—not to each other, mind you, because they still weren’t talking, but to the Pope. After a few months, the leaders announced their intention to resume full diplomatic relations.

It wasn’t the first time a mediator has played a key role in history, but that initial exchange exemplified the Mediator Pattern beautifully. The mediator initiates communication and gets the parties moving. They respond to him or her, and the mediator orchestrates the next step.

Examining the Mediator Pattern

In diplomacy, labor negotiations, and marriages, mediators are usually brought in to resolve trouble. In software design, the Mediator Pattern is a way to avoid it. While society works best when people talk freely with each other, a software system is the opposite: fewer lines of communication mean fewer ways to go wrong.

The pattern consists of several colleagues, which may perform different or similar tasks, and a single mediator, which orchestrates the overall effort. In its purest form, this orchestration consists only of managing the interactions between the colleagues and does not involve any business logic.

It just so happens that an opportunity to use this pattern has arisen during your work on the JavaScript conference’s website. The organizers know that the caffeine-buzzed participants will want a way to keep busy during the few minutes between sessions. They have asked you to design and write a game for this purpose.

You decide to make it a two-person game rather than a solo exercise. After all, one reason people go to conferences is to network. Not having joysticks or other gaming devices, both players will be at the same keyboard.

The game will go like this. As in Figure 21.5, a matrix of nodes will be connected by paths. Several bots (the medium-sized dots) will wander the graph. There will also be two larger dots to represent the two players. It will be the players’ task between them to encounter, and thereby knock off the board, every bot as quickly as possible. One player will use the keys 1, 2, 3 and 4 to navigate left, up, down and right, respectively. The other will use 6, 7, 8, and 9.

images

Figure 21.5  

A game is a classic scenario for the Mediator Pattern. These are the colleagues and their responsibilities:

  • player—Moves from its current node to a connected one, and informs the mediator that it has done so
  • bot—Does the same as a player
  • gameLogic—Contains the logic of the game, including determining when the game is over. It lays out the board in “normalized space” and puts players and bots on it. Normalized space is a square that is one unit across. It is up to the svgDisplay (described next) to present this in the browser.
  • svgDisplay—Draws the game on an svg element. Receives instructions to do so from the mediator, but does not communicate anything back to the mediator.

And coordinating the actions of the colleagues is mediator, which

  • Initializes the game by instantiating the gameLogic (with players and bots) and an svgDisplay.
  • Hooks up the event listener for the keys.

When a player or bot says it has moved, the mediator informs gameLogic and svgDisplay. When gameLogic says the game is over, the mediator unhooks the event listener.

Characteristic of the Mediator Pattern, when a player encounters a bot, it does not evict the bot on its own. Instead, it merely moves to the new node and informs the mediator that it has done so. The mediator passes this information to the gameLogic, which detects the collision and removes the bot. Yet even then, the gameLogic does not adjust the display; that’s done through another message to mediator, which then tells svgDisplay to remove the bot.

Enhancing the Reliability of Mediator-Based Code

In this book, we have emphasized test-driven development, illustrating it with relatively small fragments of a hypothetical, larger system. For the current example, we offer the fully developed game in this chapter’s downloads, but will touch on only the highlights of the development process.

Developing a Colleague

Listing 21-8 shows one of the colleagues: the player.

Typical of the Mediator Pattern, the player is instantiated with a mediator already in hand (third line of the listing). The mediator could be a complex object so this seems to introduce a broad coupling. However, you will soon see how the Interface Segregation Principle and judicious unit-testing mitigate this evil.

The simple getId, getNode, and setNode methods need no explanation (although if you haven’t seen it before, you might be interested in the little trick of attaching a static variable, nextId, to the function when the variable is declared).

The move method is what the mediator will wire to a keydown event by calling the activate function. When move is invoked, it first checks that the requested movement is possible. If it is, it calls setNode with the new location and then informs the mediator that it should do whatever needs to be done when a player moves.

Testing a Colleague

Although the player object knows a little about the topography of the game (only what nodes are in the immediate vicinity), think of all the things it does not know. It doesn’t know when the game is over, nor how many other players there are, nor anything about bots. It certainly doesn’t know the game is displayed in SVG. All this ignorance is what makes player easy to test (see Listing 21-9).

As usual, the output of successful unit tests do a great job of explaining in plain English what the object does (see Figure 21.6).

images

Figure 21.6  

The overview is plain enough, but there are a few interesting things to notice about the tests.

First, at the very top of the listing, notice the fakeMediator.

fakeMediator = {
  onPlayerMoved: function() {}
};

A mediator of some sort is necessary to initialize the player (tenth line of the listing):

player = Game.player(fakeMediator);

However, if the unit tests completely cover player’s code (which they do), and they use a fake that has only the functions needed for player, the unit tests also verify that player only needs those functions. This allows you to segregate the mediator’s interface, as you read in Chapter 16.

Segregating the Mediator’s Interfaces

Where can you install and enforce a segregated interface? In the ContractRegistry, of course. This was also covered in Chapter 16, expanded in Chapter 17, and used a little in Chapter 19, but this game is the first full example of its pervasive use.

Listing 21-10 shows how the mediator’s interfaces are established.

You may recall the suggestion in the “Putting It All Together” section of Chapter 17 that you could use a module like the one above to supply contracts and validators. By convention, such a module would have getContracts and attachValidators methods. Then, a contract registry for the application would consume the methods from all such modules to assemble the contract registry. (In this chapter’s downloads, Mediator\GameContractRegistry.js is that auto-assembling registry. You will see more of it shortly.)

The preceding mediatorContracts.js file has three contracts for the mediator: mediatorForPlayer, mediatorForBot and mediatorForLogic. The mediatorForPlayer interface requires only that the thing under consideration be an object and that it have an onPlayerMoved method.

Deciding Where to Put the Contracts

We have put the mediatorForPlayer contract in mediatorContracts.js, but you might feel that it belongs in playerContracts.js. The idea is not without merit. Suppose that the mediator and player modules are maintained by different people or even different teams. Now suppose that mediator changes in a way that requires a revision to the mediatorForPlayer contract. (This violates the Open/Closed Principle from Chapter 1, but sometimes life is not ideal.) Whoever makes the change might change the contract and think his job is done. The player object will verify that the incoming mediator fulfills the contract, but the contract no longer reflects player’s original expectations!

In contrast, suppose the player object (and its programmers) owned the mediatorForPlayer contract. That is, suppose the contract was in playerContracts.js instead of mediatorContracts .js. When mediator’s programmers make their change, they will not change the contract, which is outside of their domain. If the change breaks the contract, player’s unit tests will fail and player’s programmers will know they should adjust their code.

The idea that an interface is owned by the module that consumes it rather than by the module that fulfills it is part of some formulations of the Dependency Inversion Principle. The main disadvantage is that every module that consumes the interface must duplicate its description. That violates the DRY Principle.

So you have a choice. You can put contracts like mediatorForPlayer in mediatorContracts.js so mediator’s programmers know what might be a breaking change, or you can put them in the contract modules for the consuming objects so their unit tests will fail appropriately when mediator changes.

Wherever the contract resides, the next step is to put it to use.

Ensuring the Colleague Gets a Mediator with the Expected Interface

The mediatorContracts.js module created a mediatorForPlayer interface. Now playerContracts.js can ensure that whenever a player is created, it gets such a mediator (see Listing 21-11):

The first two lines of the attachValidators method cause the mediatorForPlayer contract to be checked against the argument to Game.player:

registry.attachArgumentsValidator(
  'player', Game, ['mediatorForPlayer']);

If something else is passed in, an exception is thrown. In return, player promises that it will return an object that meets the 'player' contract:

registry.attachReturnValidator(
  'player', Game, 'player');

That contract is a promise to provide the methods you saw in player:

function isPlayer(thing) {
  return typeof thing === 'object' &&
         typeof thing.getId === 'function' &&
         typeof thing.setNode === 'function' &&
         typeof thing.getNode === 'function' &&
         typeof thing.activate === 'function' &&
         typeof thing.deactivate === 'function';
}

Importantly, it is not a guarantee of an actual player object (using instanceof, for example). Contracts that use characteristics instead of types keep your code in the JavaScript “duck-typing” idiom. In fact, you have already seen how this was useful when creating the fakeMediator in Listing 21-9.

Code contracts do add several layers of function-calling to each method they protect. Not only does that impede performance, but it can make debugging more complex. If you would like to remove code contracts for production or temporarily during debugging, it’s as easy as removing this line from index.html:

 <script src="GameContractRegistry.js"></script>

Without that line, the contract registry is never instantiated, much less populated, and contracts will have absolutely no footprint in your running code. (They don’t run, but to keep them from loading, you can also remove the <script> tags for the contract modules, too.) For reference, Listing 21-12 is what you would be missing.

The other colleagues in the game are similar in spirit. You can find their code and unit tests in this chapter’s downloads, in the Mediator directory.

Developing a Mediator

The mediator is not a mastermind. Its role is just to get things started, make sure that every object knows what it needs to know until they decide to call it quits, and then close things down in an orderly way. Listing 21-13 shows the mediator for the game.

When the mediator is instantiated, it creates the gameLogic and svgDisplay colleagues (at the end of the listing). The gameLogic, in turn, creates the players and the bots.

The mediator that other modules will see is in the med variable midway down the listing and returned at the end.

As you continue to look at the player colleague, you see that the flow of control goes like this:

  1. The activate method adds an event listener that will cause the appropriate player’s move method to be called when the right key is pressed.
  2. As you have seen, player.move, upon a successful transfer to a new node, calls mediator .onPlayerMoved.
  3. That method tells both the game logic and the display that the player has moved.
  4. Steps 2 and 3 continue until finally the gameLogic object detects that the game is over and calls mediator.endGame, which will unhook the event listener.

Testing the Mediator

The mediator’s job involves lots of interaction with objects it’s not supposed to know a great deal about, so you can guess that Jasmine spies will abound. Listing 21-14 shows some of the details.

In the early part of the listing, fakes are set up for the display, the players, and the game logic. All the mediator’s tests care about is that these objects are called as expected, and spies are created for that purpose in the next part of the listing:

spyOn(Game,'svgDisplay').and.returnValue(fakeDisplay);
spyOn(Game,'gameLogic').and.returnValue(fakeLogic);
spyOn(fakeDisplay,'onPlayerMoved');
spyOn(fakeDisplay,'endGame');
spyOn(fakeLogic,'onPlayerMoved');

The first pair of spies intercept the creation of svgDisplay and gameLogic, causing the fakes to be returned. The remaining spies will detect when onPlayerMoved is called on those objects, and when the game ends. There is no callThrough, callFake, or returnValue on those spies because the only matter of interest is whether mediator has made the calls.

The tests of onPlayerMoved demonstrate how this plays out:

describe('onPlayerMoved(player)', function() {
  var player;
  beforeEach(function() {
    var mediator = Game.mediator(),
        node = Game.gameNode();
    player = Game.player(mediator);
    player.setNode(node); // Pretend just moved here.
    mediator.onPlayerMoved(player);
  });
  it("informs the board of the player's new location", function() {
    expect(fakeLogic.onPlayerMoved).toHaveBeenCalledWith(player);
  });
  it("informs the display of the player's new location", function() {
    expect(fakeDisplay.onPlayerMoved).toHaveBeenCalledWith(player);
  });
});

The expectations are simply that the spies were called with the correct parameters. This is typical when unit-testing a mediator. If the tests are more complicated than just verifying that the mediator makes the appropriate calls to the appropriate colleagues, your implementation of the Mediator Pattern may not be as pure as you might wish.

SUMMARY

The Observer and Mediator Patterns are closely related and their reliable development has much in common, including the following:

The next section of this book considers a few special or exotic subjects in testing.