Featured resource
2026 Tech Forecast
2026 Tech Forecast

1,500+ tech insiders, business leaders, and Pluralsight Authors share their predictions on what’s shifting fastest and how to stay ahead.

Download the forecast
  • Lab
    • Libraries: If you want this lab, consider one of these libraries.
    • Core Tech
Labs

Guided: Building and Testing Reusable Blazor Components

In this guided lab, you will build reusable Blazor components and verify their behavior using automated component tests. You will create parameterized components, handle events and state updates, and write unit tests using bUnit to ensure components render and behave correctly within a Blazor application.

Lab platform
Lab Info
Level
Beginner
Last updated
Apr 23, 2026
Duration
45m

Contact sales

By clicking submit, you agree to our Privacy Policy and Terms of Use, and consent to receive marketing emails from Pluralsight.
Table of Contents
  1. Challenge

    Introduction

    Welcome to the Building and Testing Reusable Blazor Components Code Lab. In this hands-on lab, you build reusable Blazor components, create parameterized components that handle events and state updates, and write automated component tests using bUnit to verify that components render and behave correctly within a Blazor application.

    About the tools and concepts

    Blazor components are self-contained UI building blocks written in Razor (.razor files). They accept data through parameters and communicate changes back to parent components through event callbacks.

    bUnit is a testing library for Blazor components. It renders components in a lightweight test host and gives you tools to inspect the rendered markup and simulate user interactions like button clicks.

    Component lifecycle methods such as OnParametersSet run automatically at defined points during a component's life, giving you a place to validate or transform incoming parameter values before the component renders.

    Prerequisites

    Before starting this lab, you should be comfortable with:

    • Blazor component fundamentals and Razor syntax
    • C# classes, properties, and object-oriented concepts
    • Component parameters and event handling basics
    • Unit testing concepts and xUnit test structure

    The lab environment is ready to use. Run dotnet build at any time to verify your changes compile.

    The Scenario

    You are a front-end developer at CarvedRock building a shared component library for multiple Blazor applications. The team needs reusable, well-tested components for common UI patterns. Your job is to create a ProductCard component that accepts product data as parameters and notifies a parent component when the user clicks "Add to Cart", implement a CartSummary component that tracks the running item count, and write bUnit tests that verify rendering output and component behavior.

    The Component Structure

    Key files in the lab environment
    • BlazorComponents/Components/ProductCard.razor --- the main reusable product card with parameters and an event callback
    • BlazorComponents/Components/CartSummary.razor --- a stateful cart summary component
    • BlazorComponents.Tests/ProductCardTests.cs --- bUnit tests verifying ProductCard rendering and events
    • BlazorComponents.Tests/CartSummaryTests.cs --- bUnit tests verifying CartSummary state behavior
    • BlazorComponents.Tests/ComponentTestSuite.cs --- a single test that runs the complete evaluation pipeline

    Complete the tasks in order. Each task builds on the previous one.

    Run any test file with:

    dotnet test
    
  2. Challenge

    Building Reusable Components

    Understanding Blazor Parameters

    A parameter is a public property decorated with [Parameter] that lets a parent component pass data into a child component. Without parameters, every component would need to hard-code its own data, making reuse impossible. The [Parameter] attribute tells Blazor's rendering engine to populate the property whenever the parent sets a corresponding attribute on the component tag.

    Every Blazor component property you want to receive data from outside needs this attribute — omitting it means the parent's value is silently ignored and the property retains its default. ### Understanding Event Callbacks

    Parameters carry data into a component. EventCallback<T> carries notifications out of it. When the user clicks a button inside ProductCard, the component needs a way to inform whoever is using it — that is the job of EventCallback. The generic type parameter T is the data type passed along with the notification; in this case, string carries the product title back to the parent.

    Unlike a regular C# delegate, EventCallback<T> is a Blazor-aware type that automatically triggers re-renders on the parent and handles async invocation safely. Marking it with [Parameter] exposes it exactly like a data parameter — the parent wires it up in markup.

  3. Challenge

    Implementing Lifecycle Behavior and Component State

    Understanding the OnParametersSet Lifecycle

    Blazor calls OnParametersSet every time a component receives new or updated parameter values — including the first render. This makes it the right place to validate or normalize incoming data before the component renders. The alternative is null-checks scattered throughout your markup, which is harder to maintain.

    Overriding this method follows the same pattern as any C# override: protected override void OnParametersSet(). Inside the body, you have full access to all parameter values in their updated state, so you can apply defaults, transform values, or trigger side effects cleanly in one place. ### Understanding Component State

    Unlike parameters, which come from outside, state is data a component owns and manages internally. A private field like _itemCount is not visible to the parent — the component controls it entirely through its own methods.

    When state changes, Blazor does not automatically know the UI needs to update. You call StateHasChanged() to notify the rendering engine that something changed and the component should re-render. Without that call, the UI stays stale even though the underlying data is correct. In practice, event handlers bound with @onclick trigger re-renders automatically, but methods called from outside — like AddItem, which a parent calls via a component reference — require an explicit StateHasChanged() call.

  4. Challenge

    Testing Component Rendering and Events

    Understanding bUnit Rendering

    bUnit renders a Blazor component in an in-memory test host without a running browser. Render<T> accepts a parameter builder that mirrors exactly how you would configure a component in Razor markup. Once rendered, the result exposes the full DOM through cut.Find("css-selector"), and .TextContent gives you the visible text of any element — making assertions as straightforward as reading the page.

    This approach tests the component's actual rendered output rather than its internal state, which means your tests stay valid even when you refactor the implementation, as long as the visible behavior remains the same. ### Understanding bUnit Event Simulation

    bUnit's Find("selector").Click() dispatches a synthetic click event on the matched element, triggering the component's @onclick handler exactly as a real browser click would. This lets you verify that event callbacks fire with the correct arguments --- and that the component's internal state responds correctly --- without spinning up a browser or a server.

    For methods that do not map to DOM events --- like AddItem, which a parent component calls directly via a component reference --- bUnit exposes the live component instance through cut.Instance. Calling a method on cut.Instance updates the component's state just as a real parent would.

  5. Challenge

    Testing State and Running the Full Suite

    Understanding State Tests with cut.Instance

    Testing component state requires a different approach than testing rendered output. When a parent component holds a reference to a child component and calls methods on it directly, bUnit mirrors that pattern through cut.Instance. You access the rendered component's live instance, call its public methods, and then inspect cut.Markup to verify the re-rendered output reflects the state change.

    This distinction matters: always test state changes through the component's public interface, not by setting private fields directly. Testing through cut.Instance.AddItem(...) proves the method works end to end — it increments the counter, calls StateHasChanged, and the markup updates — which is exactly what a real parent component would experience.

    Bringing It All Together

    In a real component library, you run the full test suite before every release to confirm nothing regressed. The ComponentTestSuite class orchestrates all component tests in a single method, giving you a single command to verify the entire library. It also serves as documentation: anyone reading the suite immediately sees every behavior the library guarantees.

  6. Challenge

    Conclusion

    Congratulations on completing the Building and Testing Reusable Blazor Components lab! You have built two production-ready Blazor components and verified their behavior with automated tests.

    What You Have Accomplished

    1. Declared Component Parameters --- Configured Title, Price, Description, and InStock as [Parameter] properties so parent components can pass in product data.
    2. Added an Event Callback --- Wired OnAddToCart as an EventCallback<string> so ProductCard notifies its parent when the cart button is clicked.
    3. Implemented Lifecycle Behavior --- Used OnParametersSet to guard against an empty Title, providing a reliable fallback value.
    4. Implemented Component State --- Built CartSummary with a private counter and two public methods, AddItem and ClearCart, that update the count and can be called directly from tests or parent components.
    5. Wrote Rendering Tests --- Used bUnit's Render and Find to assert that parameter values appear in the rendered markup.
    6. Wrote Event and Lifecycle Tests --- Simulated button clicks and verified callbacks fire with correct arguments; confirmed the empty-title guard works end to end.
    7. Wrote State Interaction Tests --- Called AddItem and ClearCart directly on the component instance via cut.Instance, used cut.Render() to sync the view, and verified the rendered count updates correctly.
    8. Built an Evaluation Pipeline --- Orchestrated the full test suite in ComponentTestSuite to validate the entire library in one run.

    Key Takeaways

    • Parameters make components reusable; event callbacks make them composable.
    • OnParametersSet is the right place to validate or normalize incoming parameter values.
    • Blazor re-renders automatically after @onclick handlers complete; StateHasChanged() is only needed when state changes happen outside the normal event cycle, such as in timers or async callbacks.
    • bUnit tests verify real rendering behavior without a browser, making them fast and reliable in CI pipelines.

    Experiment Before You Go

    You still have time in the lab environment. Try these explorations:

    • Add a [Parameter] public string ImageUrl { get; set; } to ProductCard and display an <img> tag in the markup
    • Write a bUnit test that verifies ProductCard with InStock = false has the disabled attribute on the button
    • Add a MaxItems parameter to CartSummary and disable AddItem once the limit is reached
    • Explore SetParametersAsync in bUnit to update parameters on a rendered component and observe re-render behavior
About the author

Angel Sayani is a Certified Artificial Intelligence Expert®, CEO of IntellChromatics, author of two books in cybersecurity and IT certifications, world record holder, and a well-known cybersecurity and digital forensics expert.

Real skill practice before real-world application

Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.

Learn by doing

Engage hands-on with the tools and technologies you’re learning. You pick the skill, we provide the credentials and environment.

Follow your guide

All labs have detailed instructions and objectives, guiding you through the learning process and ensuring you understand every step.

Turn time into mastery

On average, you retain 75% more of your learning if you take time to practice. Hands-on labs set you up for success to make those skills stick.

Get started with Pluralsight