first steps with FakeItEasy and problems with Action type

5.1k Views Asked by At

I have the following (here simplified) code which I want to test with 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(); 
        }
    }
}

I want to test that the ServiceException is thrown so i setup my test like that

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

With the following code

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

I get the following message

The current proxy generator can not intercept the specified method for the following reason: - Sealed methods can not be intercepted.

If I call the method directly on the service like

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

I get this message

This is a DynamicProxy2 error: The interceptor attempted to 'Proceed' for method 'Void TransactionalExecutionOf(System.Action)' which has no target. When calling method without target there is no implementation to 'proceed' to and it is the responsibility of the interceptor to mimic the implementation (set return value, out arguments etc)

Now I am a bit confused and don't know what to do next.

1

There are 1 best solutions below

2
On BEST ANSWER

Problems comes from the way you create fake and what you later expect it to do:

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

What base method? FakeItEasy has no idea what the base class is, and hence the DynamicProxy2 exception in your second case. You can create partial mock this way:

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

Note that we're basing on actual implementation, not interface anymore

This however introduces a new set of problems, as methods on ActionExecutor are not virtual and therefore interceptor cannot hook up to well - intercept them. To make your current setup work, you'll have to change your ActionExecutor and make (all) the methods virtual.

However, you may (or even should) want to avoid modifications of existing code (which sometimes might not even be an option). You could then set up your IActionExecutor fake like this:

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

This will allow you to work on faked object, with the exception of call to TransactionalExecutionOf which will be redirected to actual implementation.