A RetroSearch Logo

Home - News ( United States | United Kingdom | Italy | Germany ) - Football scores

Search Query:

Showing content from https://docs.deno.com/examples/bdd_tutorial/ below:

Behavior-Driven Development (BDD)

Behavior-Driven Development (BDD) is an approach to software development that encourages collaboration between developers, QA, and non-technical stakeholders. BDD focuses on defining the behavior of an application through examples written in a natural, ubiquitous language that all stakeholders can understand.

Deno's Standard Library provides a BDD-style testing module that allows you to structure tests in a way that's both readable for non-technical stakeholders and practical for implementation. In this tutorial, we'll explore how to use the BDD module to create descriptive test suites for your applications.

Introduction to BDD Jump to heading#

BDD extends Test-Driven Development (TDD) by writing tests in a natural language that is easy to read. Rather than thinking about "tests," BDD encourages us to consider "specifications" or "specs" that describe how software should behave from the user's perspective. This approach helps to keep tests focused on what the code should do rather than how it is implemented.

The basic elements of BDD include:

Using Deno's BDD module Jump to heading#

To get started with BDD testing in Deno, we'll use the @std/testing/bdd module from the Deno Standard Library.

First, let's import the necessary functions:

import {
  afterAll,
  afterEach,
  beforeAll,
  beforeEach,
  describe,
  it,
} from "jsr:@std/testing/bdd";
import { assertEquals, assertThrows } from "jsr:@std/assert";

These imports provide the core BDD functions:

We'll also use assertion functions from @std/assert to verify our expectations.

Writing your first BDD test Jump to heading#

Let's create a simple calculator module and test it using BDD:

calculator.ts

export class Calculator {
  private value: number = 0;

  constructor(initialValue: number = 0) {
    this.value = initialValue;
  }

  add(number: number): Calculator {
    this.value += number;
    return this;
  }

  subtract(number: number): Calculator {
    this.value -= number;
    return this;
  }

  multiply(number: number): Calculator {
    this.value *= number;
    return this;
  }

  divide(number: number): Calculator {
    if (number === 0) {
      throw new Error("Cannot divide by zero");
    }
    this.value /= number;
    return this;
  }

  get result(): number {
    return this.value;
  }
}

Now, let's test this calculator using the BDD style:

calculator_test.ts

import { afterEach, beforeEach, describe, it } from "jsr:@std/testing/bdd";
import { assertEquals, assertThrows } from "jsr:@std/assert";
import { Calculator } from "./calculator.ts";

describe("Calculator", () => {
  let calculator: Calculator;

  
  beforeEach(() => {
    calculator = new Calculator();
  });

  it("should initialize with zero", () => {
    assertEquals(calculator.result, 0);
  });

  it("should initialize with a provided value", () => {
    const initializedCalculator = new Calculator(10);
    assertEquals(initializedCalculator.result, 10);
  });

  describe("add method", () => {
    it("should add a positive number correctly", () => {
      calculator.add(5);
      assertEquals(calculator.result, 5);
    });

    it("should handle negative numbers", () => {
      calculator.add(-5);
      assertEquals(calculator.result, -5);
    });

    it("should be chainable", () => {
      calculator.add(5).add(10);
      assertEquals(calculator.result, 15);
    });
  });

  describe("subtract method", () => {
    it("should subtract a number correctly", () => {
      calculator.subtract(5);
      assertEquals(calculator.result, -5);
    });

    it("should be chainable", () => {
      calculator.subtract(5).subtract(10);
      assertEquals(calculator.result, -15);
    });
  });

  describe("multiply method", () => {
    beforeEach(() => {
      
      calculator = new Calculator(10);
    });

    it("should multiply by a number correctly", () => {
      calculator.multiply(5);
      assertEquals(calculator.result, 50);
    });

    it("should be chainable", () => {
      calculator.multiply(2).multiply(3);
      assertEquals(calculator.result, 60);
    });
  });

  describe("divide method", () => {
    beforeEach(() => {
      
      calculator = new Calculator(10);
    });

    it("should divide by a number correctly", () => {
      calculator.divide(2);
      assertEquals(calculator.result, 5);
    });

    it("should throw when dividing by zero", () => {
      assertThrows(
        () => calculator.divide(0),
        Error,
        "Cannot divide by zero",
      );
    });
  });
});

To run this test, use the deno test command:

deno test calculator_test.ts

You'll see output similar to this:

running 1 test from file:///path/to/calculator_test.ts
Calculator
  ✓ should initialize with zero 
  ✓ should initialize with a provided value 
  add method
    ✓ should add a positive number correctly 
    ✓ should handle negative numbers 
    ✓ should be chainable 
  subtract method
    ✓ should subtract a number correctly 
    ✓ should be chainable 
  multiply method
    ✓ should multiply by a number correctly 
    ✓ should be chainable 
  divide method
    ✓ should divide by a number correctly 
    ✓ should throw when dividing by zero 

ok | 11 passed | 0 failed (234ms)
Organizing tests with nested describe blocks Jump to heading#

One of the powerful features of BDD is the ability to nest describe blocks, which helps organize tests hierarchically. In the calculator example, we grouped tests for each method within their own describe blocks. This not only makes the tests more readable, but also makes it easier to locate issues when the test fails.

You can nest describe blocks, but be cautious of nesting too deep as excessive nesting can make tests harder to follow.

Hooks Jump to heading#

The BDD module provides four hooks:

beforeEach/afterEach Jump to heading#

These hooks are ideal for:

In the calculator example, we used beforeEach to create a new calculator instance before each test, ensuring each test starts with a clean state.

beforeAll/afterAll Jump to heading#

These hooks are useful for:

Here's an example of how you might use beforeAll and afterAll:

describe("Database operations", () => {
  let db: Database;

  beforeAll(async () => {
    
    db = await Database.connect(TEST_CONNECTION_STRING);
    await db.migrate();
  });

  afterAll(async () => {
    
    await db.close();
  });

  it("should insert a record", async () => {
    const result = await db.insert({ name: "Test" });
    assertEquals(result.success, true);
  });

  it("should retrieve a record", async () => {
    const record = await db.findById(1);
    assertEquals(record.name, "Test");
  });
});
Gherkin vs. JavaScript-style BDD Jump to heading#

If you're familiar with Cucumber or other BDD frameworks, you might be expecting Gherkin syntax with "Given-When-Then" statements.

Deno's BDD module uses a JavaScript-style syntax rather than Gherkin. This approach is similar to other JavaScript testing frameworks like Mocha or Jasmine. However, you can still follow BDD principles by:

  1. Writing clear, behavior-focused test descriptions
  2. Structuring your tests to reflect user stories
  3. Following the "Arrange-Act-Assert" pattern in your test implementations

For example, you can structure your it blocks to mirror the Given-When-Then format:

describe("Calculator", () => {
  it("should add numbers correctly", () => {
    
    const calculator = new Calculator();

    
    calculator.add(5);

    
    assertEquals(calculator.result, 5);
  });
});

If you need full Gherkin support with natural language specifications, consider using a dedicated BDD framework that integrates with Deno, such as cucumber-js.

Best Practices for BDD with Deno Jump to heading# Write your tests for humans to read Jump to heading#

BDD tests should read like documentation. Use clear, descriptive language in your describe and it statements:


describe("User authentication", () => {
  it("should reject login with incorrect password", () => {
    
  });
});


describe("auth", () => {
  it("bad pw fails", () => {
    
  });
});
Keep tests focused Jump to heading#

Each test should verify a single behavior. Avoid testing multiple behaviors in a single it block:


it("should add an item to the cart", () => {
  
});

it("should calculate the correct total", () => {
  
});


it("should add an item and calculate total", () => {
  
  
});
Use context-specific setup Jump to heading#

When tests within a describe block need different setup, use nested describes with their own beforeEach hooks rather than conditional logic:


describe("User operations", () => {
  describe("when user is logged in", () => {
    beforeEach(() => {
      
    });

    it("should show the dashboard", () => {
      
    });
  });

  describe("when user is logged out", () => {
    beforeEach(() => {
      
    });

    it("should redirect to login", () => {
      
    });
  });
});


describe("User operations", () => {
  beforeEach(() => {
    
    if (isLoggedInTest) {
      
    } else {
      
    }
  });

  it("should show dashboard when logged in", () => {
    isLoggedInTest = true;
    
  });

  it("should redirect to login when logged out", () => {
    isLoggedInTest = false;
    
  });
});
Handle asynchronous tests properly Jump to heading#

When testing asynchronous code, remember to:

it("should fetch user data asynchronously", async () => {
  const user = await fetchUser(1);
  assertEquals(user.name, "John Doe");
});

🦕 By following the BDD principles and practices outlined in this tutorial, you can build more reliable software and solidify your reasoning about the 'business logic' of your code.

Remember that BDD is not just about the syntax or tools but about the collaborative approach to defining and verifying application behavior. The most successful BDD implementations combine these technical practices with regular conversations between developers, testers, product and business stakeholders.

To continue learning about testing in Deno, explore other modules in the Standard Library's testing suite, such as mocking and snapshot testing.


RetroSearch is an open source project built by @garambo | Open a GitHub Issue

Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo

HTML: 3.2 | Encoding: UTF-8 | Version: 0.7.4