This is the third installment in a series of building a Test-Driven Grocery List application using Jasmine and RequireJS. To learn more about the intent and general concept of the series please visit The Making of a Test-Driven Grocery List Application in JavaScript: Part I

Introduction

In the previous article, I addressed adding the first feature to the Grocery List application: Add Item. Trying my best to adhere to the TDD/BDD philosophy, a story and a couple scenarios were drummed up prior to implementation development using language similar to that described in Dan Worth’s Introducing BDD article. Once passing, the code written within the test was moved to its own file(s) with dependencies updated in the specs and tests run again to confirm passing against the implementations.

I will take the same approach in adding a new feature in this article: Mark-Off Item.

I suspect, however, that I may actually end up creating stories for more of what can be considered integration. Since the last post, I stumbled upon a great blog from Chris Parsons that covers BDD (with a bend toward Cucumber), and in particular the article Cucumber: the integration test trap provided some great insight into the difference between acceptance and integration testing. In the previous article, I suppose more of what was done could be considered acceptance testing. Some implementation details are testing against in the Add-Item feature (for instance, the item API on the list-controller), but I sort of threw in extra code and UX/UI implementation details at the end just so I could make sure the code was actually usable in a real-life scenario. At the time, I felt that writing specs for the integration of features – what i consider more fine-grained in testing that an element is added to the DOM upon an API call – would muddle down the intent of this series. I might take back that assumption. That may lead to longer posts… you have been forewarned :)

Mark-Off Feature

There are basically 3 states to an item in the grocery store:

  1. unpossessed
  2. unpurchased
  3. owned

Once owned… well it either gets eaten or is freed into the wild – through those seemingly impenetrable automatic doors of unpurchased state, into the world of natural lighting. As well, prior to own-ed-ship, the item is in limbo as to whether or not it can reach that state – it might be placed back on the shelf to be forever unpossessed again. (You don’t want me to go over the possessed state.) Not unlike the life a a loaf of bread in a store, so too are the states of the grocery list items of the Grocery List application.

By adding an item – the feature completed in the previous article – a User has essentially identified an item not in possession, with the end goal of being owned. As a User of the application, we want to be able to denote the item as being in possession, but not purchased. The unpurchased state will notify other Users of the list that it no longer needs to be obtained. However, it will still be allowed to be unmarked and back to unpossessed – say if someone got Miracle Whip mayo instead of Helmann’s (whole ‘nother argument).

So we have essentially defined the feature for this post in the series: being able to mark and unmark an item added to the grocery list.

// story

Story: Item is marked off on grocery list

In order to remember what items have already made it to the cart
As a grocery shopper
I want to mark an item as being attained on the grocery list.

Similarly, since the item will still be available from the Grocer List application and only considered marked off, we could write up another story of being able to unmark an item from the list. For brevity’s sake, I should probably do that and if we were actually incorporating DSLs and tools that would facilitate in BDD testing, I probably would. But for the sake of this article, we’ll leave that as an added scenario to this feature.

// spec

Scenario 1: Item is marked off on grocery list
Given a user has saved an item to the list
When she requests to mark off the item
Then the item is presented differently to the user, denoting in possession
And the item is not removed from the list

Okay. I’ll admit. The desired outcome from that scenario is a little vague. But the scenarios I am writing – as discussed before, after having read this article from Chris Parsons – are more loosely acceptance criteria. As a developer, it makes me squirm a little because I know there is much more that can be described in this scenario, especially as it pertains to UI and UX. Aspects such as those can be considered more as integration points and I may circle back for more scenarios involving more of what we, as developers, are affirming in our tests (ie, design and functionality).

Anyway, undoing as well:

// spec

Scenario 2: Item is un-marked off on grocery list
Given a user has saved an item to the list
And she has marked off an item
When she requests to un-mark off the item
Then the item is presented to the user as not being in possession
And the item is not removed from the list

Basically, we are defining an item of the Grocery List application as a toggle control. Now, I can sum that up in a tiny single sentence and wave my hand, but I think we will find that there is a lot more to that statement as we start writing our tests and ultimately will change the design of the application – fo the better, I hope!

Test

To start, the story in question and described above adds to the API of the list-controller we defined and created in the previous article. The list-controller will need to expose a way to mark off an item that is saved to the list, and a way to unmark an item that was previously marked. As I see it, that’s two new methods – let’s call them markOffItem and unmarkOffItem. Let’s flesh them out and their signatures in a new spec for this feature:

/test/jasmine/spec/markitem.spec.js

define(['script/controller/list-controller'], function(listController) {



  describe('User Requests to mark-off existing item', function() {



    var name = 'apples',

        savedItem,

        markOffSpy,

        unmarkOffSpy;



    beforeEach( function() {

      listController.createNewItem();

      listController.editFocusedItem(name);

      listController.saveFocusedItem();



      savedItem = listController.itemList[listController.itemList.length-1];



      markOffSpy = spyOn(listController, "markOffItem").andCallThrough();

      unmarkOffSpy = spyOn(listController, "unmarkOffItem").andCallThrough();

    });



    ...



    afterEach( function() {

      savedItem = undefined;

      markOffSpy.reset();

      unmarkOffSpy.reset();

      listController.itemList = [];

      listController.editableItem = undefined;

    });



  });



});

I wanted to start off this test example with just showing to setup and teardown for the feature as it introduces the concept of spies. The beforeEach() of the above specification suite starts off similarly to the other spec we created for the Add Item feature – create, edit and save an item on the list-controller. Following that, and specifically on line 17 and 18, we create spies for the API modifications we are making on the list-controller in order to add the Mark Off Item feature – markOffItem() and unmarkOffItem(). Spies, in as far as they are used in this instance, are essentially wrappers on a function that can record invocation calls and provide another API to facilitate in affirming expectations of how a method should be used. If you are unfamiliar with spies, both Jasmine and Sinon have some great documentation.

The spies are defined to “call through” to the function implementation on the list-controller, as well. This will allow for the benefit of recording invocation and defining expectations of the actual implementation. Before we get into the real implementation, let’s take a look at the first spec for this test – marking off an item:

/test/jasmine/spec/markitem.spec.js

it('should denote item as being in possession', function() {

  var previouslySavedItem = savedItem,

      savedItemID = savedItem.id,

      savedItemSpy = sinon.spy();



  savedItemSpy(previouslySavedItem);

  listController.markOffItem(savedItemID);



  // spy expectations.

  expect(markOffSpy).toHaveBeenCalled();

  expect(markOffSpy).toHaveBeenCalledWith(savedItemID);



  // model expectations.

  expect(previouslySavedItem.hasOwnProperty('marked')).toBe(true);

  expect(previouslySavedItem.marked).toBe(true);

  // OR >

  sinon.assert.calledWith(savedItemSpy, sinon.match.hasOwn('marked', true));

});

With this spec, we are affirming the signature of markOffItem() method on the list-controller, as well as revealing the need for modifications to the attributes on the model of a grocery list item. I wanted to highlight how the markOffSpy() is being used in expectations, and – though not technically needed – also maybe provide some intrigue (hopefully not confusion) in using spies from SinonJS as well.

The following expectations, taken from the above spec, facilitate more in describing how listController.markOffItem() is to be used in the application, particularly that the method should be called with only one argument and that being an ID of an item from the list :

expect(markOffSpy).toHaveBeenCalled();

expect(markOffSpy).toHaveBeenCalledWith(savedItemID);

Some may say that such expectations are superfluous, and in some ways I do agree. Essentially, this line already defines its usage:

listController.markOffItem(savedItemID);

If the test didn’t cough at that line, that we can relatively assume the expectations called into question. To each his own, however. In fact, part of me thinks I should go even more overboard – ensuring only one argument was called, that it was of the same type as the id attribute on the model, etc. Anyway, after the expectations on the spy, we verify an update to the model on an attribute we have yet to define as well. From this test, we have defined it as being a boolean value and accessible on the marked property. I included a spy created using SinonJS as well, just to show off it’s capabilities and compare and contrast testing on the new attribute of the model:

var previouslySavedItem = savedItem,

      savedItemSpy = sinon.spy();

savedItemSpy(previouslySavedItem);

...

sinon.assert.calledWith(savedItemSpy, sinon.match.hasOwn('marked', true));

A spy is created on the model object, and after marking it off through the list-controller API, it is verified as being of type boolean and value of true using a sinon assert. Neat stuff. In this case, I probably would have just stuck with the Jasmine expectations, but I wanted to show you the power of SinonJS as it can provide a more robust testing API.

Rambling on. There’s still two other specifications we need to cover in this suite:

/test/jasmine/spec/markitem.spec.js

it('should retain the item in the grocery list', function() {

  listController.markOffItem(savedItem.id);



  expect(listController.itemList.length).toBe(1);

  expect(listController.itemList.indexOf(savedItem)).toBe(0);

});



it('should have marked-off item available to unmark-off', function() {

  var previouslySavedItem = savedItem,

      savedItemID = savedItem.id;



  listController.markOffItem(savedItem.id);

  listController.unmarkOffItem(savedItem.id);



  // stubbed expectations.

  expect(unmarkOffSpy).toHaveBeenCalled();

  // model expectations.

  expect(previouslySavedItem.marked).not.toBe(true);

});

These specs define the expectations of the list remaining unmodified when marking off an item, and that the model is updated appropriately when unmarking off an item through the list-controller API, respectively.

[note]
_I am using Array.prototype.indexOf in the first spec from the previous example. That didn’t make an appearance until JavaScript 1.6 and as such is not implemented in some older browsers (i have no <3 for IE<9). As I mentioned in the first article of this series, I am taking for granted that the application will be viewed on modern browsers, and in particular modern mobile browsers._

Failing

If you added this spec to the spec runner like so:

/test/jasmine/specrunner.html

require( ['spec/newitem.spec.js', 'spec/markitem.spec.js'], function() {

  var jasmineEnv = jasmine.getEnv(),

       ...

  jasmineEnv.execute();

});

it will fail. miserably. But that’s okay! That’s expected. Spies require an actual implementation on the object you are spying, and if you recall from the first spec described, we actually request a call through on the implementation in order to verify expected functionality.

Let’s get to the list-controller and grocery-ls-item model, as those are the two items addressed in the latest spec suite as needing modifications to pass.

Implementation

In our Mark Off Item spec suite, we identified two additions to the API of the list-controller. Those methods are:

    • markOffItem( itemID )
    • unmarkOffItem( itemID )

The first instinct is to just add these to the list-controller object and add an internal method that traverses the held itemList array to locate models that have the passed itemID value. Not a bad instinct, and something of the following would get the tests passing:

/script/controller/list-controller.js

markOffItem: function(itemID) {

  var item = findItemByID(itemID, this.itemList),

        renderer = findRendererByItem(item, $itemList.children());

  item.marked = true;

  $(renderer).css('text-decoration', 'line-through');

},

unmarkOffItem: function(itemID) {

  var item = findItemByID(itemID, this.itemList),

        renderer = findRendererByItem(item, $itemList.children());

  item.marked = false;

  $(renderer).css('text-decoration', 'none');

}

> where findByItemID() locates the model associated with the itemID within the itemList array and findRendererByItem() locates the DOM element associated with the model.

If you were to run that, the tests would pass. Well, technically, if you left that bit in there where a SinonJS spy was asserting on the model, it would not pass. But if you took that out, all green. Even without modifying the grocery-ls-item model… such is the dynamic nature of JavaScript. For brevities sake, let’s add the marked property to the Object.defineProperties object upon creation of a new model:

/script/model/grocery-ls-item.js

var properties = function(id) {

    return {

      "id": {

        value: id,

        writable: false,

        enumerable: true

      },

      "name": {

        value: '',

        writable: true,

        enumerable: true

      },

      "marked": {

        value: false,

        writable: true,

        enumerable: true

      }

    };

  };

passing mark-off item spec
grocery list app with mark off

Tagged: 0.1.3 https://github.com/bustardcelly/grocery-ls/tree/0.1.3

Before you leave!

I am not satisfied with the current state of the list-controller; it has far too much responsibility in managing list item renderers and models and lacks the finer details to properly keep the 1:1 relationship that renderers have to their models. We could just throw more code in there, more tests and let it grow into this gross beast, but I really think it is up for a good refactor.

So, that is what I have planned for the next article. We’ll refactor the list-controller to have less responsibility in managing each grocery-ls-item model and pass that off to a new list-item-controller. In fact, I already have started upon such a refactor and had included most of it in this article, but I felt it was getting too long (as usual) and taking away from the Mark Off Item feature presented here.

Until then, you have passing tests and a functioning Grocery List application to play with if you check it out of the repo.

Link Dump

Post Series

grocery-ls github repo
Part I – Introduction
Part II – Feature: Add Item
Part III – Feature: Mark-Off Item
Part IV – Feature: List-Item-Controller
Part V – Feature: List-Controller Refactoring
Part VI – Back to Passing
Part VII – Remove Item
Part VIII – Bug Fixing
Part IX – Persistence
Part X – It Lives!

Reference

Test-Driven JavaScript Development by Christian Johansen
Introducing BDD by Dan North
Cucumber: the integration test trap by Crhis Parsons
Testing spies for Jasmine and Sinon
RequireJS
AMD
Jasmine
Sinon
Jasmine.Async
the infamous eye-roller

Posted in JavaScript, RequireJS, grocery-ls, jasmine, unit-testing.