3

我有以下(这里是简化的)代码,我想用FakeItEasy进行测试。

public class ActionExecutor : IActionExecutor
{
    public void TransactionalExecutionOf(Action action)
    {
        try
        {
           // ...  
           action();
           // ... 
        }
        catch
        {
           // ...
           Rollback();
        }
    }

    public void Commit()
    {    }

    public void Rollback()
    {    }
}

public class Service : IService
{
    private readonly IRepository _repository;

    private readonly IActionExecutor _actionExecutor;

    // ctor for CI

    public void ServiceMethod(string name)
    {
        _actionExecutor.TransactionalExecutionOf(() =>
        {
            var item = _repository.FindByName(ItemSpecs.FindByNameSpec(name));
            if (item == null) throw new ServiceException("Item not found");

            item.DoSomething();
            _actionExecutor.Commit(); 
        }
    }
}

我想测试是否ServiceException被抛出,所以我像这样设置我的测试

var repo = A.Fake<IRepository>();
A.CallTo(() => repo.FindByName(A<ISpec<Item>>.Ignored))
 .Returns(null);

var executor = A.Fake<IActionExecutor>();
executor.Configure()
        .CallsTo(x => x.Rollback()).DoesNothing();
executor.Configure()
        .CallsTo(x => x.Commit()).DoesNothing();
executor.Configure()
        .CallsTo(x => x.TransactionalExecutionOf(A<Action>.Ignored))
        .CallsBaseMethod();

使用以下代码

var service = new Service(executor, repo);
service.ServiceMethod("notExists")
       .Throws(new ServiceException());

我收到以下消息

当前代理生成器无法拦截指定方法,原因如下: - 无法拦截密封方法。

如果我直接在服务上调用该方法,例如

var service = new Service(executor, repo);
service.ServiceMethod("NotExists");

我收到这条消息

这是一个 DynamicProxy2 错误:拦截器试图为没有目标的方法“Void TransactionalExecutionOf(System.Action)”“继续”。当调用没有目标的方法时,没有“继续”的实现,拦截器有责任模仿实现(设置返回值、输出参数等)

现在我有点迷茫,不知道下一步该怎么做。

4

1 回答 1

6

问题来自您创建假货的方式以及您以后期望它做的事情:

var executor = A.Fake<IActionExecutor>();
// ...
executor.Configure()
    .CallsTo(x => x.TransactionalExecutionOf(A<Action>.Ignored))
    .CallsBaseMethod();

什么基础方法?FakeItEasy 不知道基类是什么,因此DynamicProxy2在您的第二种情况下是例外。您可以通过这种方式创建部分模拟

var executor = A.Fake<ActionExecutor>();

请注意,我们基于实际实现,不再是接口

然而,这引入了一系列新问题,因为方法ActionExecutor不是虚拟的,因此拦截器无法很好地连接 - 拦截它们。为了使您当前的设置正常工作,您必须更改您ActionExecutor的方法并使(所有)方法成为虚拟方法。

但是,您可能(甚至应该)希望避免修改现有代码(有时甚至可能不是一种选择)。然后你可以像这样设置你的IActionExecutor假货:

var executor = A.Fake<IActionExecutor>();
A.CallTo(() => executor.TransactionalExecutionOf(A<Action>.Ignored))
    .Invokes(f => new ActionExecutor()
        .TransactionalExecutionOf((Action)f.Arguments.First())
    );

这将允许您处理伪造的对象,除了TransactionalExecutionOf将被重定向到实际实现的调用。

于 2012-01-29T15:23:24.230 回答