简体   繁体   中英

Dependency injection in unit testing, xUnit

This might be a stupid question, but I'm having one of those days.

I have a class called JwtManager and an interface called IJwtManager , I'm trying to inject the interface into a unit-testing class so I can test it but it doesn't work, the compiler gives me this message: The following constructor parameters did not have matching fixture data: IJwtManager jwtManager .

I think the problem is the compiler doesn't know what class should the interface be mapped to, but how can I write the equivalent of services.AddScoped<>() in a test class?

Code sample of test class:

public class JwtManagerTests
{
    private readonly IJwtManager jwtManager;
    public JwtManagerTests(IJwtManager jwtManager)
    {
        this.jwtManager = jwtManager;
    }

    [Fact]
    public void Test_Something()
    {
        // Test here
    }
}

Using a DI container with a unit test project is possible, but really overkill. It forces you to share dependencies between tests, which makes adjusting a dependency's behavior to suit a particular test more difficult, as you end up breaking other tests.

Instead, we should strive to define the dependencies of our system under test right there in our test method. You mentioned that your JwtManager class has a dependency on IConfiguration. No problem.

[Fact]
public void JwtManagerShouldCreateJwtWhenDetailsAreValid()
{
    // Arrange
    var configuration = new ConfigurationBuilder().AddInMemoryCollection(
    new Dictionary<string, string>
    {
        { "section:key1", "value1" },
        { "section:key2", "value2"}
    }).Build();

    var jwtManager = new JwtManager(configuration);

    // Act
    var result = jwtManager.CreateJwt("some input");

    // Assert
    result.Should().BeAValidJwt();
}

One nice thing about this is you don't have to add any configuration to your configuration object except exactly what the test needs. And if a different test of JwtManager needs different configuration settings to test a different scenario, that's easily doable now, since the configuration isn't coming from one single IoC container.

I'd caution against directly depending on IConfiguration in your classes though. It's generally better to depend on a strongly typed class instead. It makes the dependencies a lot clearer (you don't have to open up JwtManager to see what settings it would have used from IConfiguration) and the syntax is cleaner in the tests. For example:

public class JwtManagerSettings
{
    public string SomeSetting { get; set; }

    public int SomeOtherSetting { get; set; }
}

public class JwtManager
{
    readonly JwtManagerSettings _settings;

    public JwtManager(JwtManagerSettings settings)
    {
        _settings = settings;
    }
}

[Fact]
public void JwtManagerShouldCreateJwtWhenDetailsAreValid()
{
    // Arrange
    var settings = new JwtManagerSettings
    {
        SomeSetting = "abc",
        SomeOtherSettings = 123
    };

    var jwtManager = new JwtManager(settings);

    // Act
    var result = jwtManager.CreateJwt("some input");

    // Assert
    result.Should().BeAValidJwt();
}

In your actual app, Microsoft.Extensions.Configuration is fully capable of resolving a complex type like JwtManagerSettings from an IConfiguration. Wherever you configure your IoC container (such as Startup.cs) you can do this:

//assuming your settings are defined in a JSON key called "JwtManager"
var jwtManagerSettings = configuration.Get<JwtManagerSettings>("JwtManager");
services.AddSingleton(jwtManagerSettings);
{
    "JwtManager": {
        "SomeSetting": "abc",
        "SomeOtherSetting": 123
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM