The core of component testing is the mount() function — it takes your component, renders it in a real browser, and returns a Cypress chainable that you interact with using the same cy.get(), .click(), .type(), and .should() commands you already know from E2E tests. The power lies in what you pass to mount(): different props, event handlers, context providers, and slots — each creating a different test scenario without any backend or navigation.
Mounting Components — Props, Events and Rendering Variations
Every component test follows the same pattern: mount with specific props, interact, assert on the rendered output.
// ── Example component: ProductCard.tsx ──
/*
interface ProductCardProps {
name: string;
price: number;
imageUrl: string;
inStock: boolean;
onAddToCart: (productName: string) => void;
}
export function ProductCard({ name, price, imageUrl, inStock, onAddToCart }: ProductCardProps) {
return (
<div className="product-card" data-cy="product-card">
<img src={imageUrl} alt={name} data-cy="product-image" />
<h3 data-cy="product-name">{name}</h3>
<p data-cy="product-price">${price.toFixed(2)}</p>
{inStock ? (
<button data-cy="add-to-cart" onClick={() => onAddToCart(name)}>
Add to Cart
</button>
) : (
<span data-cy="out-of-stock" className="out-of-stock">Out of Stock</span>
)}
</div>
);
}
*/
// ── Component test: ProductCard.cy.tsx ──
import { ProductCard } from './ProductCard';
describe('ProductCard', () => {
// ── Test 1: Renders basic product information ──
it('should display product name, price and image', () => {
cy.mount(
);
cy.get('[data-cy="product-name"]').should('have.text', 'Sauce Labs Backpack');
cy.get('[data-cy="product-price"]').should('have.text', '$29.99');
cy.get('[data-cy="product-image"]').should('have.attr', 'alt', 'Sauce Labs Backpack');
});
// ── Test 2: Shows "Add to Cart" when in stock ──
it('should show Add to Cart button when in stock', () => {
cy.mount(
);
cy.get('[data-cy="add-to-cart"]').should('be.visible').and('contain.text', 'Add to Cart');
cy.get('[data-cy="out-of-stock"]').should('not.exist');
});
// ── Test 3: Shows "Out of Stock" when not in stock ──
it('should show Out of Stock when not in stock', () => {
cy.mount(
);
cy.get('[data-cy="out-of-stock"]').should('be.visible');
cy.get('[data-cy="add-to-cart"]').should('not.exist');
});
// ── Test 4: Fires onAddToCart event when clicked ──
it('should call onAddToCart with product name when button is clicked', () => {
const onAddToCartStub = cy.stub().as('addToCartHandler');
cy.mount(
);
cy.get('[data-cy="add-to-cart"]').click();
// Verify the stub was called with the correct argument
cy.get('@addToCartHandler').should('have.been.calledOnceWith', 'Bike Light');
});
// ── Test 5: Handles edge case — zero price ──
it('should display $0.00 for free products', () => {
cy.mount(
);
cy.get('[data-cy="product-price"]').should('have.text', '$0.00');
});
// ── Test 6: Handles edge case — very long name ──
it('should handle very long product names without layout break', () => {
const longName = 'A'.repeat(200);
cy.mount(
);
cy.get('[data-cy="product-card"]').should('be.visible');
// Verify the card does not overflow its container
cy.get('[data-cy="product-card"]').then(($card) => {
const cardWidth = $card[0].offsetWidth;
const parentWidth = $card[0].parentElement!.offsetWidth;
expect(cardWidth).to.be.at.most(parentWidth);
});
});
});
cy.stub() is Cypress’s built-in function spy/stub. When passed as an event handler prop (onAddToCart={cy.stub().as('handler')}), it records every call with its arguments. You can then assert: cy.get('@handler').should('have.been.calledOnceWith', 'Bike Light'). This is how you verify that a component fires the correct events without actually implementing the handler logic. Stubs replace the real function with a recording — no side effects, full visibility into what was called.<ProductCard price={0} />, <ProductCard name="" />. This is where component testing provides the highest unique value — testing rendering edge cases that E2E cannot easily reproduce.cy.stub() for event handler props, not anonymous functions like () => {}. An anonymous function works (the component does not crash), but you cannot assert on whether it was called or with what arguments. cy.stub().as('name') gives you a named reference that you can assert on: cy.get('@name').should('have.been.called'). Without the stub, you verify rendering but not behaviour.Common Mistakes
Mistake 1 — Not testing event handler callbacks
❌ Wrong: Mounting a button component, clicking it, but not verifying that the onClick prop was called with the correct arguments.
✅ Correct: Passing cy.stub().as('onClick') as the prop, clicking the button, then asserting: cy.get('@onClick').should('have.been.calledOnceWith', expectedValue).
Mistake 2 — Only testing the happy-path props
❌ Wrong: Testing ProductCard only with valid name, price, and inStock=true — missing all edge cases.
✅ Correct: Testing with inStock=false, price=0, empty name, very long name, missing image URL, and negative price to verify the component handles every prop variation defensively.