4
\$\begingroup\$

Introduction

I'm writing unit tests for an extension method I wrote. Its only purpose it is to wrap startup logic which extends ViewLocationExpanders list by an instance implementating IViewLocationExpander. ViewLocationExpanders is a property of RazorViewEngineOptions, which can be configured in application startup in ConfigureServices() method. I'm using XUnit 2.4.1.

Usage

Instead of:

services.Configure<RazorViewEngineOptions>(options =>
{
    options.ViewLocationExpanders.Add(new ViewLocationExpander());
});

I can use:

services.AddViewLocationExpander(new ViewLocationExpander());

ViewLocationExpander

public class ViewLocationExpander : IViewLocationExpander
{
    public IEnumerable<string> ExpandViewLocations(
        ViewLocationExpanderContext context, 
        IEnumerable<string> viewLocations)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (viewLocations == null)
        {
            throw new ArgumentNullException(nameof(viewLocations));
        }

        /*
            * Note:
            * {0} = action name
            * {1} = controller name
            * {2} = area name
            */

        var newViewLocations = new string[]
        {
            // Example: '/Views/Home/_Partials/FooBar.cshtml'
            "/Views/{1}/_Partials/{0}.cshtml",
        };

        // Add new locations *AFTER* MVC default locations.
        return viewLocations.Union(newViewLocations);
    }

    public void PopulateValues(ViewLocationExpanderContext context)
    {
        context.Values["customviewlocation"] = nameof(ViewLocationExpander);
    }
}

Extension method

public static IServiceCollection AddViewLocationExpander(
    this IServiceCollection services, 
    IViewLocationExpander viewLocationExpander)
{
    if (services == null)
    {
        throw new ArgumentNullException(nameof(services));
    }

    if (viewLocationExpander == null)
    {
        throw new ArgumentNullException(nameof(viewLocationExpander));
    }

    return services.Configure<RazorViewEngineOptions>(options =>
    {
        options.ViewLocationExpanders.Add(viewLocationExpander);
    });
}

Unit tests

[Fact]
public void ExtensionMethodAddsNewViewLocationExpander()
{
    // Arrange
    var services = new ServiceCollection();
    services.AddMvc();

    // These two are required to active the RazorViewEngineOptions.
    services.AddSingleton<IHostingEnvironment, HostingEnvironment>();
    services.AddSingleton<ILoggerFactory, LoggerFactory>();

    // Act
    var serviceProvider = services.BuildServiceProvider();
    var oldOptions = serviceProvider.GetRequiredService<IOptions<RazorViewEngineOptions>>().Value;
    services.AddViewLocationExpander(new ViewLocationExpander());
    serviceProvider = services.BuildServiceProvider();
    var newOptions = serviceProvider.GetRequiredService<IOptions<RazorViewEngineOptions>>().Value;

    // Assert
    Assert.True(newOptions.ViewLocationExpanders.Count > oldOptions.ViewLocationExpanders.Count);
}

Questions

  1. Am I going beyond the scope of what unit testing should include? I'm afraid that my code is actually testing the basic functions of generic collections and/or aspects of ASP.NET Core.
  2. If my concern above is true, that should I write unit tests to this at all? How should it work?
\$\endgroup\$
4
\$\begingroup\$

I find your test is excelent (and I'll borrow it from you). I would, however, make it a litte bit more exact and not only test the counts of generated locations but also whether they have the expected values.

You are not testing basic functions of collections or ASP.NET-Core aspects because it doesn't matter how it's implemented. What you are testing is whether your application has all the view locations it needs to work correctly.

This is a very important (integration) test and you should not remove it but improve it by asserting the actual locations too. If this breaks then your app wont't work anymore so it's a real lifesaver.

\$\endgroup\$
  • \$\begingroup\$ Thank you for answer. Maybe I should write further integration test checking if appropriate views are found with the new ViewLocationExpanderadded? But that would probably be outside the scope of unit testing and secondly inside that unit test method there is not much more to test (or at least I see it that way in debugger). And please, go ahead and borrow anything you want from my post. I'm happy it's useful. \$\endgroup\$ – Prolog Apr 19 at 21:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.