Lesson 2.7: Adding a Test Mocking Framework

With our current test project, we have several classes that we created to “mock” some simple behavior for our tests: MockGameSession, MockJSRuntime, and MockIconProvider. As we build out our game, we will need to mock more classes to help simplify our testing. Creating mock objects manually is repetitive and time consuming, so to increase our productivity, we will try automatic generation of mock objects by using a mocking framework.

A mocking framework is used to create replacement objects like Fakes, Stubs and Mocks. It is used to isolate each dependency and help developers perform unit testing in a concise, quick, and reliable way.

There are several different mock frameworks available for .NET. But for our project, we’re going use the Moq framework.

Installing Moq Framework

To install the Moq package, we need to use the NuGet Package Manager again. Launch it by right clicking on the SimpleRPG.Game.Tests project and select Manage NuGet Packages from the context menu.

Fig 1 – Installing Moq package

Then Browse for the Moq package and click the Install button (accepting any license requests). This will install the Moq framework into our test project.

Remove MockJSRuntime & MockIconProvider

Both of these classes are mocked as part of registering Blazorise for component tests (in Lesson 2.4) in the TestServiceProviderExtensions.AddBlazoriseServices extension method. These classes just implement a couple of interfaces and do nothing when their methods are called. This is the simplest form of mock objects.

We are going to delete these two classes, and then use Moq to create mock instances of these two interfaces. The new code in the TestServiceProviderExtensions.cs file will look like:

using Blazorise;
using Blazorise.Bootstrap;
using Bunit;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.JSInterop;
using Moq;

namespace SimpleRPG.Game.Tests.Mocks
{
    public static class TestServiceProviderExtensions
    {
        public static void AddBlazoriseServices(this TestServiceProvider services)
        {
            var mockJsRuntime = new Mock<IJSRuntime>().Object;

            services.AddSingleton<IClassProvider>(new BootstrapClassProvider());
            services.AddSingleton<IStyleProvider>(new BootstrapStyleProvider());
            services.AddSingleton<IJSRunner>(new BootstrapJSRunner(mockJsRuntime));
            services.AddSingleton<IJSRuntime>(mockJsRuntime);
            services.AddSingleton<IComponentMapper>(new ComponentMapper());
            services.AddSingleton<IThemeGenerator>(new BootstrapThemeGenerator());
            services.AddSingleton<IIconProvider>(new Mock<IIconProvider>().Object);
            services.AddSingleton<BlazoriseOptions>(new BlazoriseOptions());
        }
    }
}

The first thing we’ll notice is that this file has been greatly simplified. We deleted our mock implementation of IJSRuntime and IIconProvider. The remaining code is just the code that registers the Blaorise Bootstrap services.

Line #14 above creates a new instance of of a mock for the ISJRuntime interface. The Mock class (which is part of the Moq framework) is a generic class, so that we can define what type we are mocking. The implementation of this new mock class provides properties and methods defined by the interface which return default values and perform no operations on method calls… just like we did in our handwritten mock classes. The Mock<T>.Object property casts our mock object to the type we used to define the generic definition.

Note that Moq works really well with interfaces and classes… we can use either in Mock<T> definition. However when wanting to overwrite the implementation of methods/properties, we need to use interfaces or classes with virtual methods/properties that Moq will override. This does put design constraints on our implementation, but coding to interfaces is a good design practice anyway to help keep our components more loosely coupled.

Then, we use the mockJsRuntime variable in calls to register types for for IJSRunner and IJSRuntime.

Finally on line #22, we replace the use of our MockIconProvider with another definition of the Moq type. This time Mock<IIconProvider> gives us a simple mock implementation of the IIconProvider interface.

Removing MockGameSession

Let’s look at a more complicated mocking scenario. To this point our mock objects have had no implementation. But what about when we need to mock a object that has some operation or data that needs to be returned and used in our calling code? We can still use the Moq framework to return the test data that we require, but with just a little more setup.

Defining IGameSession Interface

As we described earlier, coding to an interface is a good design pattern to follow when we want to hide the implementation and allow two loosely coupled components to work together via the interface definition. This is what we want for the GameSession. It makes our code more robust and makes it easier to mock the view model.

First, we’ll define the IGameSession interface by creating a new C# source file. We will define the public interface that we want to use to communicate between the MainScreen page and the GameSession view model.

using SimpleRPG.Game.Engine.Models;

namespace SimpleRPG.Game.Engine.ViewModels
{
    public interface IGameSession
    {
        Player CurrentPlayer { get; }

        void AddXP();
    }
}

For now, our interface just has the CurrentPlayer property and the AddXP method.

Then, we need to use the interface in the definition of our GameSession view model.

using SimpleRPG.Game.Engine.Models;

namespace SimpleRPG.Game.Engine.ViewModels
{
    public class GameSession : IGameSession
    {
        public Player CurrentPlayer { get; private set; }

        public GameSession()
        {
            this.CurrentPlayer = new Player
            {
                Name = "DarthPedro",
                CharacterClass = "Fighter",
                HitPoints = 10,
                Gold = 1000,
                ExperiencePoints = 0,
                Level = 1
            };
        }

        public void AddXP()
        {
            this.CurrentPlayer.ExperiencePoints += 10;
        }
    }
}

I’m not going to go into all of the details of using an interface, but notice that in line #5 where we define the GameSession class, we say that it inherits from IGameSession. This allows us to cast between the two types. And the GameSession class must implement methods and properties that match the definitions in the IGameSession interface.

Note: There is also a good description of using interfaces in the WPF version of this game sample.

References Using IGameSession

Now that we have the interface, we need to replace direct references to GameSession with references to IGameSession. This gives us the ability to replace the implementation of IGameSession in our tests.

Therefore, change Program.ConfigureAppServices to register the interface and the implementation class with the DI container.

        private static void ConfigureAppServices(IServiceCollection services)
        {
            // add app-specific/custom services here...
            services.AddSingleton<IGameSession, GameSession>();
        }

Then, change the MainScreen page to @inject the interface rather than the concrete type.

@page "/"
@inject IGameSession ViewModel

<Row Style="height: 5vh; min-height: 32px">
    <Column ColumnSize="ColumnSize.Is12" Style="background-color: aliceblue">
        <Heading Size="HeadingSize.Is3">Simple RPG</Heading>
    </Column>
</Row>
<Row Style="height: 60vh">
    <Column ColumnSize="ColumnSize.Is3.OnWidescreen.Is12" Style="background-color: aquamarine">
        <PlayerComponent Player="@ViewModel.CurrentPlayer" />
        <Button Color="Color.Secondary" Outline="true" Clicked="@ViewModel.AddXP">Add XP</Button>
    </Column>
    <Column ColumnSize="ColumnSize.Is9.OnWidescreen.Is12" Style="background-color: beige">
        Game Data
    </Column>
</Row>
<Row Style="height: 30vh">
    <Column ColumnSize="ColumnSize.Is3.OnWidescreen.Is12" Style="background-color: burlywood">
        Inventory/Quest
    </Column>
    <Column ColumnSize="ColumnSize.Is9.OnWidescreen.Is12" Style="background-color: lavender">
        Combat/Movement Controls
    </Column>
</Row>

Update the MainScreen Test

Now that our code implements and uses the IGameSession interface, we change our MainScreenTests class to also use the interface as well.

First, we are going to delete the MockGameSession class that we implemented earlier, because we’re going to replace it with a Mock<T> implementation instead.

Then, we will update the MainScreenTests class to the following:

using Bunit;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using SimpleRPG.Game.Engine.Models;
using SimpleRPG.Game.Engine.ViewModels;
using SimpleRPG.Game.Pages;
using SimpleRPG.Game.Tests.Mocks;
using Xunit;

namespace SimpleRPG.Game.Tests.Pages
{
    public class MainScreenTests
    {
        private readonly Mock<IGameSession> session = new Mock<IGameSession>();

        public MainScreenTests()
        {
            session.SetupGet(p => p.CurrentPlayer).Returns(
            new Player
            {
                Name = "TestPlayer",
                CharacterClass = "TestClass",
                Level = 1,
                HitPoints = 8,
            });
        }

        [Fact]
        public void SimpleRender()
        {
            // arrange
            using var ctx = new TestContext();
            ctx.Services.AddBlazoriseServices();
            ctx.Services.AddSingleton<IGameSession>(session.Object);

            // act
            var cut = ctx.RenderComponent<MainScreen>();

            // assert
            var expected = @"<th scope=""col"" class="""" style="""" blazor:onclick=""2"" rowspan=""2"">";
            Assert.Contains(expected, cut.Markup);
            Assert.Contains("Player Data", cut.Markup);
            Assert.Contains("TestPlayer", cut.Markup);
            Assert.Contains("TestClass", cut.Markup);
        }
    }
}

In this class, we define a member variable for our IGameSession mock, just like we did earlier in this lesson. Then in our test constructor, we configure some information in that mock object by calling Mock<T>.SetupGet. SetupGet allows up to change the behavior of the property getter for our IGameSession.CurrentPlayer property. Whenever our test requests the CurrentPlayer, we will return the instance of the test Player that we create in this constructor. This allows us to customize the behavior and data provided by our mock object without having to create our own implementation of the class.

Finally, when we register our service with the bUnit TestContext, we must register the interface (as we did in the Program class) not the implementation class. The remainder of our test code and validation remains the same.

In conclusion, we have added a powerful tool in our testing capabilities. We can use the Moq framework to mock types from our game engine and also mock interfaces defined by Blazor and ASP.NET, like we did with IJSRuntime. Being able to easily mock types that we don’t own (like system interfaces) allows us to build more isolated, predictable tests.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s