ifinlay
ifinlay

Reputation: 651

Why is my mocked logger shown as invoked and yet also not?

My first foray into mocking in a WPF application. The code I'm testing is part of an MVVM ViewModel Method and looks like this:

try
{
    var airframesForRegistration = this.UnitOfWork.Airframes.GetAirframesForRegistration(this.SearchRegistration);
    this.currentAirframes = new ObservableCollection<Airframe>(airframesForRegistration);
}
catch (Exception ex)
{
    this.logger.Error($"Could not access the database", ex);
    throw;
}

I want to test that

  1. the error is written to the logger service and
  2. that the exception is thrown.

To do this I'm using XUnit and Moq thus:

[Fact]
public void GetAirframesForSearchRegistration_DBAccessFail()
{
    using (var mock = AutoMock.GetLoose())
    {
        mock.Mock<IUnitOfWork>()
            .Setup(x => x.Airframes.GetAirframesForRegistration("AAAA"))
            .Throws(new DataException());

        string message = "Could not access the database";
        DataException exception = new DataException();

        mock.Mock<ILog>()
            .Setup(x => x.Error(message, exception));

        var afrvm = mock.Create<AirframesForRegistrationViewModel>();

        afrvm.SearchRegistration = "AAAA";

        Assert.Throws<DataException>(() => afrvm.GetAirframesForSearchRegistration());

        mock.Mock<ILog>()
            .Verify(x => x.Error(message, exception), Times.Exactly(1));

    }

The test fails thus:

Message: Moq.MockException : 
Expected invocation on the mock exactly 1 times, but was 0 times: x => x.Error("Could not access the database'", System.Data.DataException: Data Exception.)

Configured setups: 
ILog x => x.Error("Could not access the database", System.Data.DataException: Data Exception.)

Performed invocations: 
ILog.Warn("No Operators found in the database")
ILog.Warn("No airframe statuses found in the database")
ILog.Error("Could not access the database", System.Data.DataException: Data Exception.
   at Moq.MethodCall.Execute(Invocation invocation) in C:\projects\moq4\src\Moq\MethodCall.cs:line 120

(NB The extra ILog warnings occur elsewhere in the ViewModel and I was expecting those).

Question

This implies that the error logging was invoked and yet the test fails because it was invoked zero times! How can Moq and XUnit be set up to correctly test for this scenario?

Upvotes: 1

Views: 198

Answers (1)

Nkosi
Nkosi

Reputation: 247008

The setup of the logger arguments is the problem.

Differing instances between what is thrown and what is expected mean they wont match when the mock is invoked.

The mock unit of work is throwing a new exception. Not the one you are expecting.

[Fact]
public void GetAirframesForSearchRegistration_DBAccessFail() {
    using (var mock = AutoMock.GetLoose()) {
        //Arrange
        DataException exception = new DataException();

        mock.Mock<IUnitOfWork>()
            .Setup(x => x.Airframes.GetAirframesForRegistration("AAAA"))
            .Throws(exception);

        string message = "Could not access the database";

        mock.Mock<ILog>()
            .Setup(x => x.Error(message, exception));

        var afrvm = mock.Create<AirframesForRegistrationViewModel>();

        afrvm.SearchRegistration = "AAAA";

        //Act
        Action act = () => afrvm.GetAirframesForSearchRegistration();

        //Assert
        Assert.Throws<DataException>(act);

        mock.Mock<ILog>()
            .Verify(x => x.Error(message, exception), Times.Exactly(1));
    }
}

For a looser expectation you could have use It.IsAny<> argument matchers

[Fact]
public void GetAirframesForSearchRegistration_DBAccessFail() {
    using (var mock = AutoMock.GetLoose()) {
        //Arrange
        mock.Mock<IUnitOfWork>()
            .Setup(x => x.Airframes.GetAirframesForRegistration("AAAA"))
            .Throws(new DataException());

        string message = "Could not access the database";

        mock.Mock<ILog>()
            .Setup(x => x.Error(message, It.IsAny<DataException>()));

        var afrvm = mock.Create<AirframesForRegistrationViewModel>();

        afrvm.SearchRegistration = "AAAA";

        //Act
        Action act = () => afrvm.GetAirframesForSearchRegistration();

        //Assert
        Assert.Throws<DataException>(act);

        mock.Mock<ILog>()
            .Verify(x => x.Error(message, It.IsAny<DataException>()), Times.Exactly(1));
    }
}

Upvotes: 3

Related Questions