- Lab
-
Libraries: If you want this lab, consider one of these libraries.
- Core Tech
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 Info
Table of Contents
-
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 (
.razorfiles). 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
OnParametersSetrun 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 buildat 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
ProductCardcomponent that accepts product data as parameters and notifies a parent component when the user clicks "Add to Cart", implement aCartSummarycomponent 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 callbackBlazorComponents/Components/CartSummary.razor--- a stateful cart summary componentBlazorComponents.Tests/ProductCardTests.cs--- bUnit tests verifyingProductCardrendering and eventsBlazorComponents.Tests/CartSummaryTests.cs--- bUnit tests verifyingCartSummarystate behaviorBlazorComponents.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 -
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 insideProductCard, the component needs a way to inform whoever is using it — that is the job ofEventCallback. The generic type parameterTis the data type passed along with the notification; in this case,stringcarries 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. -
Challenge
Implementing Lifecycle Behavior and Component State
Understanding the OnParametersSet Lifecycle
Blazor calls
OnParametersSetevery 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 StateUnlike parameters, which come from outside, state is data a component owns and manages internally. A private field like
_itemCountis 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@onclicktrigger re-renders automatically, but methods called from outside — likeAddItem, which a parent calls via a component reference — require an explicitStateHasChanged()call. -
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 throughcut.Find("css-selector"), and.TextContentgives 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@onclickhandler 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 throughcut.Instance. Calling a method oncut.Instanceupdates the component's state just as a real parent would. -
Challenge
Testing State and Running the Full Suite
Understanding State Tests with
cut.InstanceTesting 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 inspectcut.Markupto 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, callsStateHasChanged, 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
ComponentTestSuiteclass 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. -
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
- Declared Component Parameters --- Configured
Title,Price,Description, andInStockas[Parameter]properties so parent components can pass in product data. - Added an Event Callback --- Wired
OnAddToCartas anEventCallback<string>soProductCardnotifies its parent when the cart button is clicked. - Implemented Lifecycle Behavior --- Used
OnParametersSetto guard against an emptyTitle, providing a reliable fallback value. - Implemented Component State --- Built
CartSummarywith a private counter and two public methods,AddItemandClearCart, that update the count and can be called directly from tests or parent components. - Wrote Rendering Tests --- Used bUnit's
RenderandFindto assert that parameter values appear in the rendered markup. - Wrote Event and Lifecycle Tests --- Simulated button clicks and verified callbacks fire with correct arguments; confirmed the empty-title guard works end to end.
- Wrote State Interaction Tests --- Called
AddItemandClearCartdirectly on the component instance viacut.Instance, usedcut.Render()to sync the view, and verified the rendered count updates correctly. - Built an Evaluation Pipeline --- Orchestrated the full test suite in
ComponentTestSuiteto validate the entire library in one run.
Key Takeaways
- Parameters make components reusable; event callbacks make them composable.
OnParametersSetis the right place to validate or normalize incoming parameter values.- Blazor re-renders automatically after
@onclickhandlers 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; }toProductCardand display an<img>tag in the markup - Write a bUnit test that verifies
ProductCardwithInStock = falsehas thedisabledattribute on the button - Add a
MaxItemsparameter toCartSummaryand disableAddItemonce the limit is reached - Explore
SetParametersAsyncin bUnit to update parameters on a rendered component and observe re-render behavior
- Declared Component Parameters --- Configured
About the author
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.