Assertions are the lifeblood of automated testing in JavaScript. While basic assertions like checking for equality are crucial, Chai, a popular assertion library, empowers us with advanced techniques to handle complex scenarios and error conditions. Let's explore these techniques with some code examples.
Imagine testing a User object with properties like name, email, and address. Checking for simple equality wouldn't suffice. Chai's deep.equal assertion ensures the entire object structure matches:
const user = {
name: "John",
email: "john@example.com",
address: { street: "123 Main St" },
};
expect(user).to.be.deep.equal({
name: "John",
email: "john@example.com",
address: { street: "123 Main St" },
});
The main difference between expect(user).to.be.equal and expect(user).to.be.deep.equal in Chai is how they compare objects:
This is why a simple equal assertion would fail in our example. While the object properties hold the same values, they aren't the same object in memory.
Not all comparisons demand exact matches. In scenarios like testing a search functionality that returns results containing a search term, Chai's closeTo assertion allows for slight variations.
For instance, consider validating a discounted price against its original price, where a small difference is acceptable. Here's how we can utilize closeTo:
const price = 9.99;
const discountedPrice = 9.85;
expect(discountedPrice).to.be.closeTo(price, 0.2);
This assertion passes because the difference between discountedPrice and price falls within the acceptable range of 0.2. Similarly, we can leverage closeTo to accommodate minor discrepancies in floating-point calculations or imprecise value matching.
Built-in assertions may not always perfectly align with our application's unique logic. Chai empowers us to create custom assertions tailored to our specific domain.
For instance, imagine testing an e-commerce application where we need to verify a product's stock availability. While Chai offers assertions for numbers, we might want a more descriptive assertion specifically for stock levels. Here's how we could create a custom assertion for inStock:
function inStock() {
const actualStock = this._obj.stock;
this.assert(
actualStock > 0,
`Expected product to be in stock, but stock level is ${actualStock}`
);
}
Assertion.addMethod("inStock", inStock);
const product = { name: "T-Shirt", price: 19.99, stock: 50 };
expect(product).to.be.inStock();
This code defines a custom assertion named inStock for Chai. The function checks if a product object's stock is positive. Assertion.addMethod integrates this logic into Chai's assertions. The final line showcases how to use to.be.inStock to verify product availability in a test.
Tests should validate successful scenarios and expected errors. Chai's throw assertion verifies exceptions of the right type are thrown:
function calculateDiscount(amount) {
if (amount < 0) {
throw new Error("Amount cannot be negative");
}
// ... discount calculation logic
}
expect(() => calculateDiscount(-10)).to.throw("Amount cannot be negative");
Chai supports chained assertions for verifying multiple properties at once:
expect(person)
.to.have.property("name")
.that.is.a("string")
.and.equals("John Doe");
expect(person)
.to.have.property("age")
.that.is.a("number")
.and.equals(30);
expect(person)
.to.have.property("gender")
.that.is.a("string")
.and.equals("male");
expect(person)
.to.have.property("occupation")
.that.is.a("string")
.and.equals("developer");
By mastering these advanced assertion techniques with Chai, we can write robust and informative tests, leading to a more reliable application. Remember, the choice of assertions depends on our specific testing needs and application domain.
The code examples are also on GitHub. Experiment away!