bUnit bUnit
Search Results for

    Show / Hide Table of Contents

    Writing Tests in C# for Blazor Components

    Testing Blazor components is a little different from testing regular C# classes: Blazor components are rendered, they have the Blazor component life cycle during which we can provide input to them, and they can produce output.

    Use bUnit to render the component under test, pass in its parameters, inject required services, and access the rendered component instance and the markup it has produced.

    Rendering a component happens through bUnit's TestContext. The result of the rendering - a IRenderedComponent<TComponent> - provides access to the component instance and the markup produced by the component.

    Creating a Basic Test

    This is a simple example that tests the following <HelloWorld> component:

    <h1>Hello world from Blazor</h1>
    
    • xUnit
    • NUnit
    • MSTest
    using Xunit;
    using Bunit;
    
    namespace Bunit.Docs.Samples
    {
      public class HelloWorldTest
      {
        [Fact]
        public void HelloWorldComponentRendersCorrectly()
        {
          // Arrange
          using var ctx = new TestContext();
    
          // Act
          var cut = ctx.RenderComponent<HelloWorld>();
    
          // Assert
          cut.MarkupMatches("<h1>Hello world from Blazor</h1>");
        }
      }
    }
    
    using Bunit;
    using NUnit.Framework;
    
    namespace Bunit.Docs.Samples
    {
      public class HelloWorldTest
      {
        [Test]
        public void HelloWorldComponentRendersCorrectly()
        {
          // Arrange
          using var ctx = new Bunit.TestContext();
    
          // Act
          var cut = ctx.RenderComponent<HelloWorld>();
    
          // Assert
          cut.MarkupMatches("<h1>Hello world from Blazor</h1>");
        }
      }
    }
    
    Note

    TestContext is an ambiguous reference - it could mean Bunit.TestContext or NUnit.Framework.TestContext - so you have to specify the Bunit namespace when referencing TestContext to resolve the ambiguity for the compiler. Alternatively, you can give bUnit's TestContext a different name during import, e.g.: using BunitTestContext = Bunit.TestContext;

    using Bunit;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    
    namespace Bunit.Docs.Samples
    {
      [TestClass]
      public class HelloWorldTest
      {
        [TestMethod]
        public void HelloWorldComponentRendersCorrectly()
        {
          // Arrange
          using var ctx = new Bunit.TestContext();
    
          // Act
          var cut = ctx.RenderComponent<HelloWorld>();
    
          // Assert
          cut.MarkupMatches("<h1>Hello world from Blazor</h1>");
        }
      }
    }
    
    Note

    TestContext is an ambiguous reference - it could mean Bunit.TestContext or Microsoft.VisualStudio.TestTools.UnitTesting.TestContext - so you have to specify the Bunit namespace when referencing TestContext to resolve the ambiguity for the compiler. Alternatively, you can give bUnit's TestContext a different name during import, e.g.:
    using BunitTestContext = Bunit.TestContext;

    The test above does the following:

    1. Creates a new instance of the disposable bUnit TestContext, and assigns it to ctx variable using the using var syntax to avoid unnecessary source code indention.
    2. Renders the <HelloWorld> component using TestContext, which is done through the RenderComponent<TComponent>(ITestRenderer, Action<ComponentParameterCollectionBuilder<TComponent>>) method. We cover passing parameters to components on the Passing Parameters to Components page.
    3. Verifies the rendered markup from the <HelloWorld> component using the MarkupMatches method. The MarkupMatches method performs a semantic comparison of the expected markup with the rendered markup.
    Tip

    Learn more about how the semantic HTML/markup comparison in bUnit works, and how to customize it, on the Customizing the Semantic HTML Comparison page.

    Tip

    In bUnit tests, we like to use the abbreviation CUT, short for "component under test", to indicate the component that is being tested. This is inspired by the common testing abbreviation SUT, short for "system under test".

    Remove Boilerplate Code from Tests

    We can remove some boilerplate code from each test by making the TestContext implicitly available to the test class, so we don't have to have using var ctx = new Bunit.TestContext(); in every test. This can be done like this:

    • xUnit
    • NUnit
    • MSTest
    using Xunit;
    using Bunit;
    
    namespace Bunit.Docs.Samples
    {
      public class HelloWorldImplicitContextTest : TestContext
      {
        [Fact]
        public void HelloWorldComponentRendersCorrectly()
        {
          // Act
          var cut = RenderComponent<HelloWorld>();
    
          // Assert
          cut.MarkupMatches("<h1>Hello world from Blazor</h1>");
        }
      }
    }
    

    Since xUnit instantiates test classes for each execution of the test methods inside them, and disposes of them after each test method has run, we simply inherit from TestContext, and methods like RenderComponent<TComponent>(ITestRenderer, Action<ComponentParameterCollectionBuilder<TComponent>>) can then be called directly from each test. This is seen in the listing above.

    using Bunit;
    using NUnit.Framework;
    
    namespace Bunit.Docs.Samples
    {
      public class HelloHelloWorldImplicitContextTest : BunitTestContext
      {
        [Test]
        public void HelloWorldComponentRendersCorrectly()
        {
          // Act
          var cut = RenderComponent<HelloWorld>();
    
          // Assert
          cut.MarkupMatches("<h1>Hello world from Blazor</h1>");
        }
      }
    }
    
    using System;
    using Bunit;
    using Bunit.Rendering;
    using Microsoft.AspNetCore.Components;
    using NUnit.Framework;
    
    namespace Bunit.Docs.Samples
    {
      public abstract class BunitTestContext : IDisposable
      {
        private Bunit.TestContext _context;
    
        public ITestRenderer Renderer => _context?.Renderer ?? throw new InvalidOperationException("NUnit has not started executing tests yet");
    
        public TestServiceProvider Services => _context?.Services ?? throw new InvalidOperationException("NUnit has not started executing tests yet");
    
        public void Dispose()
        {
          _context?.Dispose();
          _context = null;
        }
    
        [SetUp]
        public void Setup() => _context = new Bunit.TestContext();
    
        [TearDown]
        public void TearDown() => Dispose();
    
        public IRenderedComponent<TComponent> RenderComponent<TComponent>(params ComponentParameter[] parameters) where TComponent : IComponent
            => _context?.RenderComponent<TComponent>(parameters) ?? throw new InvalidOperationException("NUnit has not started executing tests yet");
    
        public IRenderedComponent<TComponent> RenderComponent<TComponent>(Action<ComponentParameterCollectionBuilder<TComponent>> parameterBuilder) where TComponent : IComponent
            => _context?.RenderComponent<TComponent>(parameterBuilder) ?? throw new InvalidOperationException("NUnit has not started executing tests yet");
      }
    }
    

    Since NUnit instantiates a test class only once for all tests inside it, we cannot simply inherit directly from TestContext as we want a fresh instance of TestContext for each test. Instead, we create a helper class, BunitTestContext, listed above, and use that to hook into NUnit's [SetUp] and [TearDown] methods, which runs before and after each test.

    Then methods like RenderComponent<TComponent>(ITestRenderer, Action<ComponentParameterCollectionBuilder<TComponent>>) can be called directly from each test, as seen in the listing above.

    using Bunit;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    
    namespace Bunit.Docs.Samples
    {
      [TestClass]
      public class HelloHelloWorldImplicitContextTest : BunitTestContext
      {
        [TestMethod]
        public void HelloWorldComponentRendersCorrectly()
        {
          // Act
          var cut = RenderComponent<HelloWorld>();
    
          // Assert
          cut.MarkupMatches("<h1>Hello world from Blazor</h1>");
        }
      }
    }
    
    using System;
    using Bunit;
    using Bunit.Rendering;
    using Microsoft.AspNetCore.Components;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    
    namespace Bunit.Docs.Samples
    {
      public abstract class BunitTestContext : IDisposable
      {
        private Bunit.TestContext _context;
    
        public ITestRenderer Renderer => _context?.Renderer ?? throw new InvalidOperationException("MSTest has not started executing tests yet");
    
        public TestServiceProvider Services => _context?.Services ?? throw new InvalidOperationException("MSTest has not started executing tests yet");
    
        public void Dispose()
        {
          _context?.Dispose();
          _context = null;
        }
    
        [TestInitialize]
        public void Setup() => _context = new Bunit.TestContext();
    
        [TestCleanup]
        public void TearDown() => Dispose();
    
        public IRenderedComponent<TComponent> RenderComponent<TComponent>(params ComponentParameter[] parameters) where TComponent : IComponent
            => _context?.RenderComponent<TComponent>(parameters) ?? throw new InvalidOperationException("MSTest has not started executing tests yet");
    
        public IRenderedComponent<TComponent> RenderComponent<TComponent>(Action<ComponentParameterCollectionBuilder<TComponent>> parameterBuilder) where TComponent : IComponent
            => _context?.RenderComponent<TComponent>(parameterBuilder) ?? throw new InvalidOperationException("MSTest has not started executing tests yet");
      }
    }
    

    Since MSTest instantiates a test class only once for all tests inside it, we cannot simply inherit directly from TestContext as we want a fresh instance of TestContext for each test. Instead, we create a helper class, BunitTestContext, listed above, and use that to hook into MSTest's [TestInitialize] and [TestCleanup] methods. This runs before and after each test.

    Then methods like RenderComponent<TComponent>(ITestRenderer, Action<ComponentParameterCollectionBuilder<TComponent>>) can be called directly from each test, as seen in the listing above.

    Important

    All the examples in the documentation explicitly new up a TestContext, i.e. using var ctx = new TestContext(). If you are using the trick above and have your test class inherit from TestContext, you should NOT new up a TestContext in test methods also.

    Simply call the test contest's methods directly, as they are available in your test class.

    For example, var cut = ctx.RenderComponent<HelloWorld>();
    becomes var cut = RenderComponent<HelloWorld>();.

    Further Reading

    With the basics out of the way, next we will look at how to pass parameters and inject services into our component under test. After that, we will cover ways we can verify the outcome of a rendering in more detail

    • Passing Parameters to Components
    • Injecting Services into Components Under Test
    • Verifying Markup from a Component
    • Verifying the State of a Component Under Test
    • Triggering Event Handlers in Components

    Editorial support provided by Packt.

    • Improve this Doc
    Back to top Documentation updated on 2/26/2021 3:36:12 PM +00:00 in commit 95ed9179f0.