Unit Testing with Jasmine
Key Concepts
- Jasmine Framework
- Test Suites
- Specs
- Expectations
- Spies
- Setup and Teardown
- Asynchronous Testing
- Matchers
- Custom Matchers
- Mocking and Stubbing
- Code Coverage
1. Jasmine Framework
Jasmine is a behavior-driven development (BDD) framework for testing JavaScript code. It provides a simple and clean syntax for writing tests.
Example:
describe("Jasmine Framework", function() { it("should be easy to use", function() { expect(true).toBe(true); }); });
Imagine Jasmine as a toolbox filled with tools (functions) designed to help you build and maintain a sturdy house (codebase) by ensuring each brick (unit) is solid.
2. Test Suites
A test suite is a collection of test cases, or "specs," that are grouped together. It is defined using the describe
function.
Example:
describe("Calculator", function() { // Specs will be defined here });
Think of a test suite as a folder that contains multiple documents (specs). Each document is a separate test case, but they are all related to the same topic (unit of code).
3. Specs
A spec is an individual test case within a test suite. It is defined using the it
function and contains one or more expectations.
Example:
describe("Calculator", function() { it("should add two numbers", function() { expect(add(1, 2)).toBe(3); }); });
Consider a spec as a single question in a quiz. Each question (spec) tests a specific aspect of the subject (unit of code).
4. Expectations
Expectations are assertions that define the expected outcome of a spec. They are defined using the expect
function and a matcher.
Example:
it("should add two numbers", function() { expect(add(1, 2)).toBe(3); });
Think of expectations as checkpoints in a race. Each checkpoint (expectation) ensures that the runner (code) is on the right track (expected outcome).
5. Spies
Spies are functions that allow you to track calls to other functions and inspect their behavior. They are useful for testing callbacks and asynchronous code.
Example:
describe("Spies", function() { it("should track calls to a function", function() { spyOn(window, 'alert'); alert("Hello, world!"); expect(window.alert).toHaveBeenCalledWith("Hello, world!"); }); });
Imagine spies as undercover agents. They observe (track) the actions (calls) of a target (function) without interfering, reporting back (inspecting) the results.
6. Setup and Teardown
Setup and teardown functions are used to prepare the environment before each spec and clean up after each spec. They are defined using beforeEach
and afterEach
.
Example:
describe("Setup and Teardown", function() { var counter; beforeEach(function() { counter = 0; }); afterEach(function() { counter = null; }); it("should increment the counter", function() { counter++; expect(counter).toBe(1); }); });
Consider setup and teardown as preparing a stage before a performance and cleaning it up afterward. Each act (spec) starts with a clean stage (setup) and leaves no trace (teardown).
7. Asynchronous Testing
Asynchronous testing allows you to test code that involves asynchronous operations, such as AJAX calls or timeouts. Jasmine provides functions like done
to handle this.
Example:
describe("Asynchronous Testing", function() { it("should handle a timeout", function(done) { setTimeout(function() { expect(true).toBe(true); done(); }, 1000); }); });
Think of asynchronous testing as waiting for a delayed train. You need to wait (handle the delay) before you can proceed (complete the test).
8. Matchers
Matchers are functions that compare the actual value with the expected value. Jasmine provides a variety of built-in matchers, such as toBe
, toEqual
, and toContain
.
Example:
it("should use matchers", function() { expect(1 + 1).toBe(2); expect([1, 2, 3]).toContain(2); });
Consider matchers as tools in a toolbox. Each tool (matcher) is designed to check a specific condition (comparison) on the workpiece (value).
9. Custom Matchers
Custom matchers allow you to define your own comparison logic. This is useful for creating reusable and expressive tests.
Example:
beforeEach(function() { jasmine.addMatchers({ toBeDivisibleByTwo: function() { return { compare: function(actual) { return { pass: actual % 2 === 0 }; } }; } }); }); it("should use custom matchers", function() { expect(4).toBeDivisibleByTwo(); });
Imagine custom matchers as specialized tools in a toolbox. Each tool (custom matcher) is tailored to perform a specific task (custom comparison) on the workpiece (value).
10. Mocking and Stubbing
Mocking and stubbing are techniques used to replace real objects or functions with test doubles. This allows you to isolate the code under test and control its behavior.
Example:
describe("Mocking and Stubbing", function() { it("should mock a function", function() { var mock = jasmine.createSpy('mock'); mock(); expect(mock).toHaveBeenCalled(); }); });
Think of mocking and stubbing as using stand-ins in a movie. The stand-ins (mocks/stubs) replace the real actors (objects/functions) to ensure the scene (test) runs smoothly.
11. Code Coverage
Code coverage is a metric that measures the percentage of code that is covered by tests. It helps you identify untested parts of your codebase.
Example:
// Imagine a code coverage report showing which lines of code are covered by tests
Consider code coverage as a map that shows which areas of a city (codebase) have been explored (tested) and which areas are still uncharted (untested).