1

I have below unit test C# code,

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();
    }
}

I have Constructor_throws_Exception which only covers one argument null exception, but not the other. How to cover both argument null exception plus the catch block for method? Is there a way I can merge with all in a single test? I am using xUnit.

enter image description here

user584018
  • 10,186
  • 15
  • 74
  • 160

1 Answers1

2

You have to create a unique test for each invalid combination. Could be something like this:

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));
}

Getting a working mock for the ILogger<> is more complicated then it seems in the first spot. The problem is, that all convenient methods are extensions methods, which can't be mocked. Under the hood, all of these methods will call the Log<TState>() method which must be mocked. Thankfully to this answer, this can be done as follows:

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);
        }
    }
}
Oliver
  • 43,366
  • 8
  • 94
  • 151
  • Thanks @Oliver. Could you please suggest how to cover `catch` block for method `DoWork()`? – user584018 Jan 17 '22 at 08:34
  • aah! gr8t learn. Thanks and appreciate! I have other question, would you mind have a look there https://stackoverflow.com/questions/70721518/how-to-write-unit-test-for-program-and-startup-cs-file-for-asp-net-core-web-api – user584018 Jan 17 '22 at 15:00