/wisdom-bot-kata-angular

Kata for learning mockist/interaction/London style TDD in AngularJS

Primary LanguageJavaScriptArtistic License 2.0Artistic-2.0

Wisdom Sharing Bot Kata

Kata for learning mockist/interaction/London style TDD in AngularJS

Setup

You will need node/npm and git installed.

You will also need Chrome (Firefox will work with a bit of configuration).

git clone https://github.com/zhon/wisdom-bot-kata-angular.git

cd wisdom-bot-kata-angular/

Grunt, karma and bower will be installed globally with the -g flag:

npm -g install grunt-cli karma bower

The rest will be installed in the current directory by calling:

npm install
bower install

You will be editing src/app/chatroom/

To run the tests and the build

grunt watch

To view your work load wisdom-bot-kata-angular/build/index.html into your browser.

For more information take a look at the seed project

Introduction

I hear you want to try Mockist style TDD. I do! I do!
What do you want to build? A chat room with a wisdom bot in AngularJS.
We start by writing an acceptance test. Yes and for now they will be manual.
Why not automate it? I will, just not in this Kata.

Starting

How do you test Hello World? Manually by checking webpage build/index.html for "Hello".
How would you write Hello World.

In src/app/chatroom/chatroom.tpl.html I write

Hello World!
Should't "World" be a variable?

Sure! I can do that with simple scope binding:

src/app/chatroom/chatroom.tpl.html

Hello {{user}}!

src/app/chatroom/chatroom.js

.controller( 'ChatroomCtrl', function ( $scope ) {
  $scope.user = 'World';
})
You didn't write a test and this is a TDD kata. I don't write tests for simple scope bindings. They couldn't possibly break.
How are we going to get the user?

The user will be entered in a form

src/app/chatroom/chatroom.tpl.html

<form>
  <label>Name:</label>
  <input ng-model="user" />
</form>
<hr/>
We need to input our message.

We will just change the form a little to get both the user and message.

src/app/chatroom/chatroom.tpl.html

<form>
  <label>Name:</label>
  <input ng-model="message.user" />
  <br />
  <label>Message:</label>
  <button type="submit">Send</button
</form>
<hr/>

And to see it we will replace Hello {{user}} with

src/app/chatroom/chatroom.tpl.html

<div class='chatbox'>
  <ul>
   <li>
    <span class='user'>
      {{message.user}}
    </span>
    <span class='message'>
        {{message.text}}
      </span>
    </li>
  </ul>
</div>

Test: Message is Saved

With the UI for entering a name and a message, what shall we do next? We will store the message (user and text) in a message repository.
Great! And...

First I write the skeleton of an angular test. It starts with a describe something (in this case a controller).

For controller tests we usually need scope.

This is followed by a beforeEach which setup up the test for the module (app.chatroom) and injects the objects we need ($rootScope and $controller).

Next we have another describe with an it inside (the actual test).

src/app/chatroom/chatroom.test.js

describe('chatroomController', function () {
  var scope  ;

  beforeEach(function () {
    module("app.chatroom");

    inject(function ( $rootScope,
                      $controller,
                    ) {
    });
  });

  describe('when a message is published it', function () {

    it ('posts to MessageRepository', function () {
    });

  });
});
After template, then?

I fill in the test. It requires a mock. I will set that up in the beforeEach as it will be used for mutiple tests.

The test is simple: assert when scope.publish is called, we post the message to the MessageRepository.

This is what the whole file looks like:

src/app/chatroom/chatroom.test.js

describe('chatroomController', function () {
  var scope, mockMessageRepository;

  beforeEach(function () {
    module("app.chatroom");

    inject(function ( $rootScope,
                      $controller,
                      MessageRepository) {
      scope = $rootScope.$new();
      mockMessageRepository = sinon.stub(MessageRepository);
      $controller("ChatroomCtrl", { $scope: scope });
    });
  });

  describe('when a message is published it', function () {

    it ('posts to MessageRepository', function () {
      scope.message = {
        'user': 'RedQueen',
        'text': 'Off with her head!'
      };
      scope.publish();
      expect(
        mockMessageRepository.post.calledWith(scope.message)
      ).toBeTruthy();
    });

  });
});
I am getting an error when I run your test.

Yes, and that error is telling me to add method post to MessageRepository

*Notice I put the .service before the bottom ;

src/app/chatroom/chatroom.js

.service( 'MessageRepository', function () {
  return {
    post: function () {
    }
  }
})
Now when you run the test, what do you see?

An Error. It is telling me I need publish on ChatroomCtrl $scope

src/app/chatroom/chatroom.js

.controller( 'ChatroomCtrl', function ( $scope ) {
  $scope.publish = function () { };
})

Test: Message is Saved - Failing

Now what do you see when running the test? I see a Failing test.Yes! Making it pass is easy.

Test: Message is Saved - Passing

Hmm.. I look forward to seeing your code passing and checked in.

No problem! I will just add one line to the controller.

src/app/chatroom/chatroom.js

$scope.publish = function () {
  messageRepository.post(message);
}
Are you ready to check in? No.
Why not? Because I still need to acceptance test it.
How will you do that? I will load the web page (build.index.html) in the browser and see that message is saved.
How? The message is not being stored.

I will add a console.log message inside of MessageRepository.post.

src/app/chatroom/chatroom.js

What happeded when you ran it?

Nothing. It isn't hooked up. I am hooking it up now by adding ng-click="publish()" to the form button.

src/app/chatroom/chatroom.tpl.html

<button type="submit" ng-click="publish()">Send</button>
Now what do you see? Both my manual and my automated test passing.
It is time to check in aand grab a snack. Mmm.... Snack!