Search code examples
c#rhino-mocks

Rhino Mocks exception "Expected #1, Actual #0" in apparently working code


I have a piece of code from an old book on MVVM which works, but a test using Rhino Mocks fails with this message:

Test method TestProject.UnitTest1.UpdateCustomer_Always_CallsUpdateWithCustomer threw exception: Rhino.Mocks.Exceptions.ExpectationViolationException: DataProvider.DoSomething(ConsoleApp.Customer); Expected #1, Actual #0

I'm providing an example which I think is equivalent:

namespace ConsoleApp {
    class Program { static void Main () { } }

    public class Customer { public string ID { get; set; } }

    public class DataProvider {
        public virtual Customer GetCustomer (string id) => new Customer ();
        public virtual void DoSomething (Customer customer) { }
    }

    public class ViewModel {
        DataProvider _dataProvider;
        Customer _customer;

        public ViewModel (DataProvider dataProvider, string id) {
            _dataProvider = dataProvider;
            _customer = new Customer { ID = id };
        }

        public void DoSomething () => _dataProvider.DoSomething (_customer);
    }
}

and the test that fails

namespace TestProject {
    [TestClass]
    public class UnitTest1 {
        [TestMethod]
        public void UpdateCustomer_Always_CallsUpdateWithCustomer () {
            DataProvider dataProviderMock = MockRepository.GenerateMock<DataProvider> ();
            Customer expectedCustomer = new Customer ();
            dataProviderMock.Stub (u => u.GetCustomer (Arg<string>.Is.Anything)).Return (expectedCustomer);
            ViewModel target = new ViewModel (dataProviderMock, string.Empty);
            target.DoSomething ();
            dataProviderMock.AssertWasCalled (d => d.DoSomething (expectedCustomer));
        }
    }
}

I read several posts on this result, e.g. 1 and 2, but this doesn't help me. I read this answer which seems interesting:

I usually get this error when a stubbed method is called with an object argument that I build in the test and in the tested code the object is built before calling that method.

While this could be a reason for the Rhino Mocks to fail, it seems to be a bug.

My question is: Is there something wrong in my test and there is a bug in Rhino Mocks, or is there a problem with my code?


Solution

  • The test stubs DataProvider.GetCustomer to return an expected customer instance to be used in the assertion, but the example view model does not invoke DataProvider.GetCustomer.

    It is using the one initialized in the constructor.

    //...
    
    public ViewModel (DataProvider dataProvider, string id) {
        _dataProvider = dataProvider;
        _customer = new Customer { ID = id };
    }
    
    //...
    

    Thus the exception thrown by the test is accurate given the shown example.

    That is because the actual instance used by the view model will not be the one used in the assertion when the test is exercised.

    //...
    
    dataProviderMock.AssertWasCalled (d => d.DoSomething (expectedCustomer));
    

    In order for the test to behave as expected based on its arrangement the view model would actually have be refactored to decouple from initializing the Customer

    For example

    public class ViewModel {
        DataProvider _dataProvider;
        string id;
    
        public ViewModel (DataProvider dataProvider, string id) {
            _dataProvider = dataProvider;
            this.id = id;
        }
    
        public void DoSomething () {
            Customer customer = _dataProvider.GetCustomer(id);
            _dataProvider.DoSomething (_customer);
        }
    }
    

    The test should also be more explicit about what it is trying to test

    [TestClass]
    public class UnitTest1 {
        [TestMethod]
        public void UpdateCustomer_Always_CallsUpdateWithCustomer () {
            //Arrange
            DataProvider dataProviderMock = MockRepository.GenerateMock<DataProvider> ();
            string id = "FakeId";
            Customer expectedCustomer = new Customer { ID = id };
            dataProviderMock.Stub (u => u.GetCustomer (id))
                .Return (expectedCustomer);
            ViewModel target = new ViewModel (dataProviderMock, id);
    
            //Act
            target.DoSomething ();
    
            //Assert
            dataProviderMock.AssertWasCalled (d => d.DoSomething (expectedCustomer));
        }
    }
    

    Alternatively, if the view model is as intended then the test needs to assert its expectations differently

    [TestClass]
    public class UnitTest1 {
        [TestMethod]
        public void UpdateCustomer_Always_CallsUpdateWithCustomer () {
            //Arrange
            DataProvider dataProviderMock = MockRepository.GenerateMock<DataProvider> ();
            string id = "FakeId";            
            ViewModel target = new ViewModel (dataProviderMock, id);
    
            //Act
            target.DoSomething ();
    
            //Assert
            dataProviderMock
                .AssertWasCalled (d => d.DoSomething (Arg<Customer>.Matches(c => c.ID == id));
        }
    }
    

    Note the delegate used in the assertion to verify the characteristics of the expected argument instead of the specific instance passed when it was invoked.