Rhino Mocks 异常 "Expected #1, Actual #0" 在明显有效的代码中

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

我有一段来自一本关于 MVVM 的旧书的代码可以运行,但是使用 Rhino Mocks 的测试失败并显示以下消息:

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

我提供了一个我认为等效的示例:

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

以及失败的测试

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

我阅读了几篇关于这个结果的帖子,例如1 and 2, but this doesn't help me. I read this answer 这看起来很有趣:

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.

虽然这可能是 Rhino Mocks 失败的原因,但它似乎是一个错误。

我的问题是:是不是我的测试有问题,Rhino Mocks有bug,还是我的代码有问题?

测试存根 DataProvider.GetCustomer 到 return 一个预期的客户实例将在断言中使用,但示例视图模型不调用 DataProvider.GetCustomer

它正在使用构造函数中初始化的那个。

//...

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

//...

因此,根据所示示例,测试抛出的异常是准确的。

这是因为视图模型使用的实际实例将不是测试时断言中使用的实例。

//...

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

为了使测试根据其安排按预期运行,实际上将重构视图模型以与初始化 Customer

分离

例如

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

测试还应该更明确地说明它要测试的内容

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

或者,如果视图模型符合预期,则测试需要以不同方式断言其期望

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

注意断言中使用的委托来验证预期参数的特征,而不是调用它时传递的特定实例。