Question

I'm trying to test some code I've written have run in to issues trying to mock a func using Machine.Fakes (which uses Moq under the hood). See the code below for an example.

public class RoutingEngine : IRoutingEngine
{
    private readonly IMessageRouters _messageRouters; 

    public RoutingEngine(IMessageRouters messageRouters)
    {
        _messageRouters = messageRouters; 
    }

    public void Route<T>(T inbound)
    {
        var messageRouters = _messageRouters.Where(x => x.CanRoute(inbound)); 
        foreach(var router in messageRouters)
            router.Route(inbound); 
    }
}

public class MessageRouters : IMessageRouters
{
    public IList<IMessageRouter> _routers = new List<IMessageRouter>();

    public IEnumerable<IMessageRouter> Where(Func<IMessageRouter, bool> func)
    {
        return _routers.Where(func); 
    }

    public void Add(IMessageRouter messageRouter)
    {
        _routers.Add(messageRouter); 
    }
}

And the test is here

public class when_routing_a_message_with_fakes : WithSubject<RoutingEngine>
{
    Establish that = () =>
    {
        Message = new MyMessage{ SomeValue= 1, SomeOtherValue = 11010 };
        Router = The<IMessageRouter>();
        Router.WhenToldTo(x => x.CanRoute(Message)).Return(true);             
        The<IMessageRouters>().WhenToldTo(x => x.Where(router => router.CanRoute(Message))).Return(new List<IMessageRouter> { Router }); 
    };

    Because of = () => Subject.Route(Message);

    It should_do_route_the_message = () => Subject.WasToldTo(x => x.Route(Param.IsAny<MyMessage>()));

    static MyMessage Message;
    static IMessageRouter Router;
}

I get an unsupported expression for the above so I changed the where method on the IMessageRouters to the following:

public IEnumerable<IMessageRouter> Where(Expression<Func<IMessageRouter, bool>> func)
{
    return _routers.Where(func.Compile()); 
}

Now I get this error

Object instance was not created by Moq.
Parameter name: mocked

Any ideas?

EDIT

So I tried writing another test without machine.fakes, as per Mocking methods with Expression<Func<T,bool>> parameter using Moq. Turns out it's an obvious problem. The func used in the real RoutingEngine is not being mocked

The<IMessageRouters>()
    .WhenToldTo(x => x.Where(router => router.CanRoute(Param.IsAny<ProcessSkuCostUpdated>())))
    .Return(new List<IMessageRouter> {Router});

The above has no bearing on the Where being executed at runtime and can't be as the func is compiled down to a private method at compile time. Seems like to mock the func, I need to push it up to an interface. Smells though as I'm pushing up internal behavior purely for testing.

Was it helpful?

Solution

I see two issues with your test code:

  1. The expression you use for setting up the Where() call on IMessageRouters is too explicit. It should not care about what exact function is passed.
  2. You are verifying whether Route() has been called on the Subject. Instead you should verify whether the Message has been passed to the IMessageRouter.

As an additional improvement, you can omit the Router field and use The<IMessageRouter>() directly.

[Subject(typeof(RoutingEngine))]
public class when_routing_a_message_with_fakes : WithSubject<RoutingEngine>
{
    Establish that = () =>
    {
        Message = new MyMessage { SomeValue = 1, SomeOtherValue = 11010 };
        The<IMessageRouter>().WhenToldTo(x => x.CanRoute(Message)).Return(true);
        The<IMessageRouters>().WhenToldTo(x => x.Where(Param<Func<IMessageRouter, bool>>.IsAnything))
            .Return(new List<IMessageRouter> { The<IMessageRouter>() });
    };

    Because of = () => Subject.Route(Message);

    It should_route_the_message = () =>
        The<IMessageRouter>().WasToldTo(x => x.Route(Message));

    static MyMessage Message;
}

OTHER TIPS

I see a way to avoid mocking the Func<> at all. I hope it's interesting to you :) Why not forget about the generalized Where(Func<>) method and provide the specific query method. You own the inbound messages and the router(s).

public class MessageRouters : IMessageRouters
{
    public IList<IMessageRouter> _routers = new List<IMessageRouter>();

    public IEnumerable<IMessageRouter> For<T>(T inbound)
    {
        return _routers.Where(x => x.CanRoute(inbound)); 
    }

    public void Add(IMessageRouter messageRouter)
    {
        _routers.Add(messageRouter); 
    }
}

The class under test becomes simpler (in signature, no Func<>).

public class RoutingEngine : IRoutingEngine
{
    private readonly IMessageRouters _messageRouters; 

    public RoutingEngine(IMessageRouters messageRouters)
    {
        _messageRouters = messageRouters; 
    }

    public void Route<T>(T inbound)
    {
        var messageRouters = _messageRouters.For(inbound); 
        foreach(var router in messageRouters)
            router.Route(inbound); 
    }
}

I'm guessing you don't need to inspect the actual instance of the inbound message either, maybe you can get away with just a Type check, like

public IEnumerable<IMessageRouter> For<T>() { ... }

and

var messageRouters = _messageRouters.For<T>();

You don't have to mock any Func<>s now and you can just do an assert-was-called (or however that looks in Moq).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top