Testing with DotNetRuleEngine

One of the goals of DotNetRuleEngine is to encourage writing testable code. DI support allows you to easily replace components with mocks and/or fakes. I'll demonstrate the concept with an example.

First, let's create a .net core Web API application with a simple controller that will use the DotNetRuleEngine.

Switch to command line. Run the following commands to create .net core Web API:

mkdir DemoWebApi && cd DemoWebApi  
dotnet new webapi  
dotnet restore  
dotnet build  

Let's open the solution, and install the DotNetRuleEngine.Core nuget package.

install-package DotNetRuleEngine.Core  

DotNetRuleEngine is also available for the standard version of .net framework.

DotNetRuleEngine uses IDependecyResolver to resolve dependencies. It is part of the DotNetRuleEngine.Interface assembly which is included when you install the nuget package.

For this example, I'll implement the dependency resolver using autofac as an IoC Container. However you can use any IoC container.

public class DependencyResolver : IDependencyResolver  
{
    private IContainer _container;
    protected ContainerBuilder ContainerBuilder = new ContainerBuilder();

    public DependencyResolver(IServiceCollection services)
    {
        ContainerBuilder.RegisterType<GetValuesRule>().AsSelf();
        ContainerBuilder.RegisterType<Service>().As<IService>();

        if (services != null)
        {
             ContainerBuilder.Populate(services);
        }
    }

    public void Build()
    {
        _container = ContainerBuilder.Build();
    }

    public object GetService(Type serviceType)
    {
        using (var scope = _container.BeginLifetimeScope())
        {
            return scope.ResolveOptional(serviceType);
        }
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        return Enumerable.Empty<object>();
    }
}

The constructor takes IServiceCollection as a parameter to register the asp.net core services with our container.

Next, register the dependency resolver in ConfigureServices of the Startup.cs

public void ConfigureServices(IServiceCollection services)  
{
    services.AddSingleton<IDependencyResolver, DependencyResolver>(provider =>
        new DependencyResolver(services));
    services.AddMvc();
}

The dependency resolver registered as a singleton. The reason for that is to use the same instance of the IDependencyResolver so that we don't re-register the dependencies with every request.

We also need to hook into the ASP.NET Core request pipeline to create a middleware to build the autofac container.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)  
{
    app.Use(async (context, next) =>
    {
        var dr = context.RequestServices.GetService<IDependencyResolver>();
        (dr as DependencyResolver).Build();
        await next.Invoke();
    });

    app.UseMvc();
}

Let's implement the GetValuesRule and the IService that were registered in DependencyResolver

public interface IService  
{
    string[] GetValues();
}

public class Service : IService  
{
    public string[] GetValues()
    {
        return new[] { "value1", "value2" };
    }
}
public class GetValuesRule : Rule<Model>  
{
    private readonly IService _service;

    public GetValuesRule(IService service)
    {
        _service = service;
    }

    public override IRuleResult Invoke()
    {
        Model.Values = _service.GetValues();

        return null;
    }
}

I've injected IService to the GetValuesRule constructor. This is resolved via the dependency resolver. And in the ValuesController.cs, I inject the IDependencyResolver to the constructor of the controller.

[Route("api/[controller]")]
public class ValuesController : Controller  
{
    private readonly IDependencyResolver _dependencyResolver;

    public ValuesController(IDependencyResolver dependencyResolver)
    {
        _dependencyResolver = dependencyResolver;
    }
}

For this example, remove all other methods in theValuesController except the IEnumarable<string> Get() method. And replace its content with the following:

[HttpGet]
public IEnumerable<string> Get()  
{
    var model = new Model();
    var results = RuleEngine<Model>.GetInstance(model, _dependencyResolver)
        .ApplyRules(typeof(GetValuesRule))
        .Execute();

    if (results.AnyError())
    {
        throw new Exception();
    }

    return model.Values;
}

Here, we're passing the type of the GetValuesRule instead of creating an instance of it. The dependency resolver creates the instance.

Our Model.cs is as simple as:

public class Model  
{
    public string[] Values { get; set; }
}

Time to test!

Create a new test project if you havent already. We're going test the Get action of our controller to ensure it is returning the expected result.

However we want to eliminate components that make external calls. Such as database, network, etc. The test should focus on testing our logic only.

For this example, let's assume the IService is a component that makes http calls over the network to retrieve data. We'll use a mock object to replace/mock the IService component to avoid making http calls during testing.

We'll use Moq as our mocking framework. Let's install that first.

install-package Moq  

Let's instantiate ValuesController with TestDependencyResolver (we'll implement it next), assert the result of the Get call, and verify our mock is called as expected.

public class UnitTest  
{
    private readonly Mock<IService> _mock = new Mock<IService>();

    [Fact]
    public void TestGet()
    {
        SetupServiceMock();

        var valuesController = new ValuesController(new TestDependencyResolver(_mock));

        var result = valuesController.Get().ToList();

        Assert.True(result.Contains("value1"));
        Assert.True(result.Contains("value2"));

        _mock.Verify();
    }

    private void SetupServiceMock()
    {
        _mock.Setup(provider => provider.GetValues())
            .Returns(() => new[] {"value1", "value2"})
            .Verifiable("Did not get called");
    }
}

Let's build the test dependency resolver.

public class TestDependencyResolver : DependencyResolver  
{
    public TestDependencyResolver(IMock<IService> mock):base(null)
    {
        ContainerBuilder.RegisterInstance(mock.Object).As<IService>();
        Build();
    }
}

We're inheriting from the DependencyResolver to reuse all registered dependencies except overriding component(s) that make external calls (IService only in our case) with mocks. Also we're calling the Build() method explicitly and passing null for IServiceCollection since we don't have a way of hooking into the ASP.NET Core request pipeline in our test.

At this point we just need to run our test and hopefully all should pass as expected. Go to your command line, navigate to the test project, and run the following command.

dotnet test  

Hope you enjoyed it...

Arif Yayalar

Software Engineer

Seattle, WA

Subscribe to {:arif, :yayalar}

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!