如何覆盖方法 DoMethod() 的构造函数参数空异常和 catch 块

how to cover both constructor argument null exception and catch block for method `DoMethod()`

我有以下单元测试 C# 代码,

public class ServiceTest
{
    public readonly Service _sut;
    private readonly Mock<IServiceClient> _serviceClientMock = new Mock<IServiceClient>();
    private readonly Mock<ILogger<Service>> _loggerMock = new Mock<ILogger<Service>>();
    public ServiceTest()
    {
        _sut = new Service(_serviceClientMock.Object, _loggerMock.Object);
    }

    [Fact]
    public void Constructor_throws_Exception()
    {
        Assert.Throws<ArgumentNullException>(() => new Service(null, null));
    }

    [Fact]
    public async Task Do_Test_For_DoMethod()
    {
        await _sut.DoMethod();
    }
}

我有 Constructor_throws_Exception,它只涵盖一个参数 null 异常,但不涵盖另一个。如何同时覆盖参数空异常和方法的 catch 块?有没有一种方法可以在一次测试中合并所有内容?我正在使用 xUnit.

您必须为每个无效组合创建一个唯一的测试。可能是这样的:

public static IEnumerable<object[]> GetInvalidConstructorArguments()
{
    yield return new object[] { new Mock<IServiceClient>().Object, null };
    yield return new object[] { null, new Mock<ILogger<Service>>().Object };
}

[Theory]
[MemberData(nameof(GetInvalidConstructorArguments))]
public void ThrowsOnNullArgument(IServiceClient serviceClient, ILogger<Service> logger)
{
    Assert.Throws<ArgumentNullException>(() => new Service(serviceClient, logger));
}

ILogger<> 获取工作模拟比在第一个位置看起来要复杂得多。问题是,所有方便的方法都是扩展方法,不能被模拟。在幕后,所有这些方法都将调用必须模拟的 Log<TState>() 方法。感谢 ,这可以按如下方式完成:

public class MyTests
{
    [Fact]
    public void ExceptionShouldBeWrittenToLog()
    {
        // Instruct service client to throw exception when being called.
        var serviceClient = new Mock<IServiceClient>();
        var exception = new InvalidOperationException($"Some message {Guid.NewGuid()}");
        serviceClient.Setup(s => s.Do()).Throws(exception);
        // Create a strict mock, that shows, if an error log should be created.
        var logger = new Mock<ILogger<MyService>>(MockBehavior.Strict);
        logger.Setup(l => l.Log(
            LogLevel.Error,
            It.IsAny<EventId>(),
            It.Is<It.IsAnyType>((o, t) => o.ToString() == exception.Message),
            It.IsAny<InvalidOperationException>(),
            It.IsAny<Func<It.IsAnyType, Exception, string>>()));

        // Setup SUT and call method.
        var service = new MyService(serviceClient.Object, logger.Object);
        service.DoSomething();

        // Check if method of logger was being called.
        logger.VerifyAll();
    }
}

public interface IServiceClient
{
    public void Do();
}

public class MyService
{
    private readonly IServiceClient serviceClient;
    private readonly ILogger<MyService> logger;

    public MyService(IServiceClient serviceClient, ILogger<MyService> logger)
    {
        this.serviceClient = serviceClient;
        this.logger = logger;
    }

    public void DoSomething()
    {
        try
        {
            serviceClient.Do();
        }
        catch (Exception ex)
        {
            logger.LogError(ex.Message);
        }
    }
}