Giter Club home page Giter Club logo

tdd-jasmine's Introduction

#TDD with Jasmine

Why is this important?

This workshop is important because:

Tests translate well from user stories. A well tested application will be more reliable, maintainable, and better designed. Any serious software should be developed with tests.

What are the objectives?

After this workshop, developers will be able to:

  • Articulate the benefits and costs of test driven development.
  • Write code that passes tests.
  • Write tests that enforce specifications.

Where should we be now?

Before this workshop, developers should already be able to:

  • Write JavaScript
  • Use Node & npm

Do You Test?

Sort yourselves into the following categories:

  1. I have used TDD, and I loved it.
  2. I have used TDD, and I hated it.
  3. I have not used TDD, but I want to.
  4. I have not used TDD, and I do not want to.

Thoughts:

  • For those of you who are negative to testing, why? What did you or would you do instead?
  • For those of you who are positive to testing, why? What problems did it solve?

Some possible responses...

  • Cons
  • Time. It's a waste of my time and effort to test.
  • It's too much. I can test just fine using the console.
  • App complexity. My app is too simple to require testing.
  • Pros
  • Bug detection. Quickly identify unanticipated errors.
  • Code Quality. Create standards for our code before writing it.
  • Time. Shorten development time through bug detection.
  • Documentation. Tests act as a documentation of sorts for how our code should work. Helpful to other developers and shareholders.
  • Jobs. Testing is a job requirement across the board.

All Together Now

When it comes to development in teams, both the benefits and the potential pitfalls of testing practices increase. Many feel that testing is essential when working on large, complex projects.

  • Take Ember.js for example. If you look at the framework's repo, it comes packaged with a ton of tests.
  • So many moving parts. And so many people contributing to them. Can you imagine how crazy this would get without testing?

Testing is another thing that's a little hard to appreciate through this class because we mostly work alone to make relatively small apps. Working together on this project, you're likely to find that you lose whole hours manually testing your app: quitting Node, re-seeding your database, starting Node, opening your app in the browser, clicking through each page. Testing eliminates this for you, and makes for a much less stressful week.

Two common pain points in the real world are creating test coverage for existing code bases and ensuring that test suites are adequately maintained as an application grows in complexity. BOTH of these can be avoided by using TDD as a tool for planning.

Do you wanna build a snowman?

For example, imagine you're Elsa from Frozen, and you want to build a snowman. Or more specifically a snowman constructor.

Before we write any of our snowman code, we're going to create a test. And before we create a test, we're going to think: What do I want my snowman constructor to do? What should the snowmen it creates be like?

//I want my snowman constructor to create a snowman object
//My winter wonderland is a friendly place, so I want each snowman to have a name
//In order for it to really be a snowman, it needs to have a carrot nose.
//It also needs stick arms.
//If the snowman is named Olaf, he should like warm hugs.

Now that we have our features outlined, we're going to start turning it into tests. (Note: Do these look like user stories to you?)

Specs

Tests follow a "describe...it" format, like so:

  • describe "A snowman"
    • it "should have a name"
    • it "should have a carrot nose"
    • it "should have stick arms"
    • describe "a snowman named Olaf"
      • it "should like warm hugs"

Today we'll be using a Javascript testing framework called Jasmine.

Jasmine

Jasmine is only one testing framework. There's Mocha, QUnit and more... However, they're all very similar. Most use the same "describe...it" syntax.

Getting set up

First, we're going to install jasmine-node globally.

It doesn't matter where you run this code:

$ npm install  -g jasmine-node

Let's start from an empty directory and see how we'd get tests set up.

Inside your project folder, create a new folder called spec:

$ mkdir spec

Inside that new folder, create a file named after your model, followed by "dash-spec":

$ touch spec/snowman-spec.js

Now, run:

$ jasmine-node spec

You should get something like:

$ jasmine-node spec


Finished in 0.001 seconds
0 tests, 0 assertions, 0 failures, 0 skipped

Scripting your Specs

Now, paste the specs you wrote earlier into the *-spec.js file you created.

If you run jasmine-node at this point, you'll just get an error. We need to properly format the code for Javascript.

A Jasmine test file will look like this:

describe("the test subject", function() {

  it("should have this quality", function() {

  });

  it("should have this other quality", function() {

  });

});

describe and it are actually functions, just like, say, console.log(). They both take a string as their first argument. The second argument is a callback function.

A describe statement's function contains a bunch of it statements.

An it statement's function contains the code that will do the actual testing. We're not going to write that code yet -- we're just going to worry about getting set up with the proper syntax.

For the snowman builder, the result would be this:

describe( "A snowman", function() {

  //My winter wonderland is a friendly place, so I want each snowman to have a name.
  it( "should have a name", function() {

  });

  //In order for it to really be a snowman, it needs to have a carrot nose.
  it("should have a carrot nose", function () {

  });

  //It also needs stick arms.
  it("should have stick arms", function () {

  });

  //If the snowman is named Olaf, he should like warm hugs.
  describe( "a snowman named Olaf", function() {

    it( "should like warm hugs", function() {

    });

  });

});

If I run this, I now get:

$ jasmine-node spec
....

Finished in 0.009 seconds
4 tests, 0 assertions, 0 failures, 0 skipped

Each green dot indicates a passing test.

If I add the --verbose flag, I get:

$ jasmine-node spec --verbose

A snowman - 4 ms
    should have a name - 1 ms
    should have a carrot nose - 0 ms
    should have stick arms - 0 ms

    a snowman named Olaf - 0 ms
        should like warm hugs - 0 ms

Finished in 0.01 seconds
4 tests, 0 assertions, 0 failures, 0 skipped

Update the code to the proper syntax

This includes adding the appropriate parentheses, double-quotes, semicolons, and functions.

When done, run jasmine-node spec with no failing tests.

The fact that these tests "pass" doesn't really mean anything since the tests don't actually test anything -- but at least we know there aren't any syntax errors.

The suite life of Jasmine

Let's break this test down according to its parts.

What you've done so far is create a test suite.

The Suite.

describe( "A snowman", function() {
  // Specs go here.
});

A "suite" is the highest-level container in our test file.

  • A suite defines what we are testing. Oftentimes, this is an object.
  • Indicated using the describe function.
  • Takes two arguments: (1) a string, (2) a function
    • (1) The string is the name of what we are testing
    • (2) The function contains the actual tests

The Specs

A suite contains specs. These are the it statements.

In the "spec," we target a specific part of the suite.

"Spec" is short for "specification", which comes from "specific", as in, "we're testing a specific part of this app." Get it?


Great Expectations!

Now that we have the pieces of our snowman builder described, we can start thinking about how we'll know that our snowmen are living up to our expectations.

Expectations are the meat-and-potatoes of our tests.

  • Begins with expect. Takes one argument that will be the input to our function.
  • Followed by a matcher (e.g., toBe), which will compare the actual output to the expected output. Below are a few different options.

Jasmine's built-in matchers

'toBe' matcher compares with ===
'toEqual' matcher
'toMatch' matcher is for regular expressions
'toBeDefined' matcher compares against `undefined`
'toBeUndefined' matcher compares against `undefined`
'toBeNull' matcher compares against null
'toBeTruthy' matcher is for boolean casting testing
'toBeFalsy' matcher is for boolean casting testing
'toContain' matcher is for finding an item in an Array
'toBeLessThan' matcher is for mathematical comparisons
'toBeGreaterThan' matcher is for mathematical comparisons
'toBeCloseTo' matcher is for precision math comparison
'toThrow' matcher is for testing if a function throws an exception
'toThrowError' matcher is for testing a specific thrown exception

One thing to note is that there isn't a built-in matcher for checking whether something is a specific type of object. There's no, expect( olaf ).toBeA(Snowman).

Instead, to test whether something is an object of a specific type, there are several things you could try:

expect( olaf.constructor.name ).toBe("Snowman");
expect( olaf instanceof Snowman).toBeTruthy();

Note: Those last two won't work with function expressions (var Snowman = function() {}); only with function declarations (function Snowman() {}).

A full list of Jasmine's native matchers can be found here.

If you're feeling adventurous, you can even create your own custom matcher.

Let's add the first expectation:

describe( "A snowman", function() {

  it( "should have a name", function() {
    var olaf = new Snowman("Olaf");
    expect( olaf.name ).toBeDefined();
  });

//...

It fails!

$ jasmine-node spec
F...

Failures:

  1) A snowman should have a name
   Message:
     ReferenceError: Snowman is not defined
   Stacktrace:
     ReferenceError: Snowman is not defined

Finished in 0.012 seconds
4 tests, 1 assertion, 1 failure, 0 skipped

That's because this script has no idea what a Snowman is -- we haven't linked in a file that describes a Snowman model yet.

We can create a snowman.js file and put this in it:

// snowman.js

function Snowman(name) {
  this.name = name;
}

module.exports = Snowman;

Next, I'm going to require that file inside my spec file:

var Snowman = require("../snowman");

describe( "A snowman", function() {

  //My winter wonderland is a friendly place, so I want each snowman to have a name.
  it( "should have a name", function() {
    var olaf = new Snowman("Olaf");
    expect( olaf.name ).toBeDefined();
  });

//...

...and now the test passes!

Review: Test-Driven Development Basics

The Process, Recontextualized

tdd flowchart

A planning-oriented approach TDD

  • Think.
    • What do we want this app/feature to do?
    • What are its components? (Think models!)
    • What properties should each of those components have? What should they be able to do?
    • What behaviors do you definitely want to avoid?
  • Write tests an outline of your app/feature using testing syntax.
    • For today, we're going to break this down even further, first writing suites and specs, then going back and adding expectations.
  • Run your tests. Seeing red.
  • Write code. How can we make this test pass?
  • Test passes. Green light.
  • Refactor and Repeat.

You write a failing test. Your next step is to write code that will make your test pass. This is kind of like Jeopardy, where the contestants are given the answer and then come up with a question for that answer: it's backwards from what we intuitively want to do, which is write passing code first and test it later.

This process is often abbreviated Red, Green, Refactor: write a failing test, write the code to make it pass, then refactor. Lather, rinse repeat.

Moving on

...at this point we would write one more expectation, then write the code to make it pass, then write the next expectation, and so on, doing everything one test at a time. Let's fast forward and see what a fully spec'ed snowman would look like.

Why is it not recommended to write all your expectations first, and then write all the code to make them pass?

Because it's pretty straightforward to write the code to make one failing test into a passing test. It's super-overwhelming to write the code to make a bunch of failing tests pass.

The final Snowman specs:

var Snowman = require("../snowman");

describe( "A snowman", function() {

  //My winter wonderland is a friendly place, so I want each snowman to have a name.
  it( "should have a name", function() {
    var olaf = new Snowman("Olaf");
    expect( olaf.name ).toBeDefined();
  });

  //In order for it to really be a snowman, it needs to have a carrot nose.
  it("should have a carrot nose", function () {
    var olaf = new Snowman("Olaf");
    expect ( olaf.features ).toContain("carrot nose");
  });

  //It also needs stick arms.
  it("should have stick arms", function () {
    var olaf = new Snowman("Olaf");
    expect ( olaf.features ).toContain("stick arms");
  });

  //If the snowman is named Olaf, he should like warm hugs.
  describe("A snowman named Olaf", function() {
    it( "should like warm hugs", function() {
      var frosty = new Snowman("Frosty");
      var olaf = new Snowman("Olaf");
      expect( olaf.hug() ).toBe( "I like warm hugs!" );
      expect( frosty.hug() ).not.toBe( "I like warm hugs!" );
    });
  });

});

Add an expectation to your code

  • Pair-up with someone in groups of two to three.
  • Together, come up with an origional expectation! What would be something fun our snowman is able to do?
  • Write down this expectations in plain English.

Note: The one who designs the spec, designs the code!

Take 5 minutes to add the expectation to the suite on your own. Then... Take 5 minutes to meet up with your group and review each others' expectations.

Note: If you have time left, pass your spec & repeat this process

Refactor

What's not DRY about my tests? What repeats?

var olaf = new Snowman("Olaf");

We can DRY up tests by making a piece of code run before each test:

describe( "A snowman", function() {
  var olaf;

  // this code get run before each spec
  beforeEach(function() {
    olaf = new Snowman("Olaf");
  });

  it( "should have a name", function() {
    expect( olaf.name ).toBeDefined();
  });

  it("should have a carrot nose and stick arms", function () {
    expect ( olaf.features ).toContain("carrot nose", "stick arms");
  });

  describe("A snowman named Olaf", function() {
    var frosty;
    it( "should like warm hugs", function() {
      frosty = new Snowman("Frosty");
      expect( olaf.hug() ).toBe( "I like warm hugs!" );
      expect( frosty.hug() ).not.toBe( "I like warm hugs!" );
    });
  });

});

Let's Build a Snowman!

// /snowman.js
function Snowman(name) {
  this.name = name;
  this.features = ["carrot nose", "stick arms"];
}

Snowman.prototype = {
  hug: function() {
    if (this.name == "Olaf") {
      return "I like warm hugs!";
    }
    else {
      return "Why are you hugging snow?";
    }
  }
};

module.exports = Snowman;

Let's run our test again: $ jasmine-node spec

$ jasmine-node spec --verbose

A snowman - 7 ms
    should have a name - 4 ms
    should have a carrot nose - 1 ms
    should have stick arms - 0 ms

    A snowman named Olaf - 1 ms
        should like warm hugs - 1 ms

Finished in 0.014 seconds
4 tests, 5 assertions, 0 failures, 0 skipped

Bonus: Asynchronous tests

One last thing to note is you can test AJAX requests and database interactions, but it's a little tricky as they're asynchronous. Make sure you use .then().

Here are some examples:

// ...
it("can be saved to the database", function() {
  var olaf = new Snowman("Olaf");
  olaf.save().then(function(err, docs){
    // err will always be null if the database action was successful
    expect( err ).toBeNull();
  });
});

it("makes a successful AJAX request", function() {
  var olaf = new Snowman("Olaf");
  $.getJSON("http://snowhub.com").then(function(response){
    expect( response[0].name ).toBe( "John Snow" );
  });
});

Quiz Questions

  • Put the following actions in order, from what should happen first to what should happen last:

    1. Write the code for your app
    • Write an English-y outline of your app's models and what they should do
    • Write passing tests for your app
    • Refactor your app's code
    • Write failing tests for your app
  • What's the difference between a suite, a spec, and an expectation?

  • What's the difference between describe, it, and expect statements?

  • What does beforeEach do?

Additional Reading

tdd-jasmine's People

Contributors

ilias-t avatar

Watchers

James Cloos avatar Toby avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.