具有简化实现的RhinoMocks存根,Try-method模式

时间:2015-05-12 11:36:16

标签: c# .net unit-testing mocking rhino-mocks

我想用RhinoMocks模拟一个非常庞大的存储库,主要是为了完全实现庞大且经常变化的界面,而不是使用VisualStudio的“实现接口”默认实现(这需要为接口更新所有模拟改变并导致很多垃圾代码。)

我目前使用Stubs,但我还没有找到如何覆盖模拟的默认方法,除了定义每个可能的输入值。当使用bool TryGet(key,out值)模式时以及当我需要默认行为时,如果找不到键(这里:return false / null,在其他情况下:抛出异常),这尤其糟糕。

有没有办法在RhinoMocks中实现方法转发?

public interface IMyRepository
{
    // would normally be implemented by database access
    bool TryGetNameById(int id, out string name);

    // more...
}

// within some other class:

public void SetupMockRepository()
{
    IDictionary<int, string> namesByIds = new Dictionary<int, string>() 
    //create and fill with mock values

    var mockRep = new MockRepository()
    var repStub = mockRep.Stub<IMyRepository>() 

    // something like this, forward inner calls,
    // without defining expected input values
    var repStub.Stub(r => r.TryGetNameById(int id, out string name)
        .OutRef((id) => (namesByIds.TryGetValue(id, out name) ? name : null))
        .Return((id) => namesByIds.ContainsKey(id)));
}

编辑:

我现在尝试了一个代表,看起来更好,但仍有问题:

private delegate bool TryGet<TKey, TValue>(TKey key, out TValue value);

public void SetupMockRepository()
{
    // code from above omitted

    string outName;
    _stub.Stub(r=>r.TryGetNameById(0, out outName))
        .IgnoreArguments()
        .Do(new TryGet<int,string>(namesByIds.TryGetValue))
}

这是可以接受的,但是当我运行它时,我得到一个InvalidOperationException: “上一个方法'TryGetValue(123)'需要返回值或异常才能抛出”

2 个答案:

答案 0 :(得分:0)

您可以随时制作自己的假物品。通过混合使用部分模拟和部分模拟,您应该获得最大的灵活性。

public interface IMyRepository
{
    bool TryGetNameById(int id, out string name);
}

public class FakeRepository : IMyRepository
{
    // public on purpose, this is for testing after all
    public readonly Dictionary<int, string> nameByIds = new Dictionary<int, string>();

    // important to make all methods/properties virtual that are part of the interface implementation
    public virtual bool TryGetNameById(int id, out string name)
    {
        return this.nameByIds.TryGetValue(id, out name);
    }
}

您的设置将成为这样:

public void Setup()
{
    var mock = MockRepository.GeneratePartialMock<FakeRepository>();
    mock.nameByIds.Add(1, "test");
}

如果您需要更复杂的行为,您仍然可以存根方法调用,因为所有方法都是virtual

答案 1 :(得分:0)

这是我的第一个部分解决方案:

模拟对象在委托设置之后需要一个.Replay(),因为在调用MockRep.ReplayAll()之前使用了这些方法(在测试本身之前)。

我目前仅在少数情况下使用期望和记录/重放逻辑(并且不完全理解它是什么样的),并且发现模拟对象在看似正确的设置上失败是令人不安的。我只想要一个带有一些重写方法/属性的默认实现,如果有人更改了接口的不相关部分,这不会导致构建失败。

另一个新问题是设置.Do()委托,如

mockObj.Stub(o => o.DoSth(x))
    .IgnoreArguments()
    .Do(new Action(x => this.DoSth(x)))

在完全不同的方法上导致期望错误,该方法具有默认存根实现且未被更改。