Как мне издеваться над Func<>, используя Machine.Fakes (Moq)?

Я пытаюсь протестировать некоторый код, который я написал, столкнулся с проблемами, пытаясь смоделировать функционал с помощью Machine.Fakes (который использует Moq под капотом). Посмотрите код ниже для примера.

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

И тест здесь

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

Я получил неподдерживаемое выражение для вышеупомянутого, поэтому я изменил метод where на IMessageRouters к следующему:

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

Теперь я получаю эту ошибку

Экземпляр объекта не был создан Moq.
Имя параметра: высмеянный

Есть идеи?

РЕДАКТИРОВАТЬ

Поэтому я попытался написать еще один тест без machine.fakes в соответствии с методами Mocking с параметром Expression>, используя Moq. Оказывается, это очевидная проблема. Функцию, используемую в реальном RoutingEngine, не высмеивают

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

Вышесказанное не имеет никакого отношения к Where, выполняемому во время выполнения, и не может быть, так как func компилируется в приватный метод во время компиляции. Похоже, издеваться над функцией, мне нужно подтолкнуть его к интерфейсу. Хотя пахнет, так как я подталкиваю внутреннее поведение исключительно для тестирования.

2 ответа

Решение

Я вижу две проблемы с вашим тестовым кодом:

  1. Выражение, которое вы используете для настройки Where() позвонить на IMessageRouters слишком явно. Не должно заботиться о том, какая именно функция передается.
  2. Вы проверяете, Route() был вызван на Subject, Вместо этого вы должны проверить, является ли Message был передан в IMessageRouter,

В качестве дополнительного улучшения вы можете опустить Router поле и использование The<IMessageRouter>() непосредственно.

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

Я вижу способ избежать насмешек над Func<> совсем. Я надеюсь, что это интересно для вас:) Почему бы не забыть об обобщенном Where(Func<>) метод и предоставить конкретный метод запроса. Вы являетесь владельцем входящих сообщений и маршрутизатора (ов).

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

Тестируемый класс становится проще (в сигнатуре нет 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); 
    }
}

Я предполагаю, что вам не нужно проверять фактический экземпляр входящего сообщения, может быть, вы можете уйти с помощью только Type проверить, как

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

а также

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

Вы не должны издеваться над Func<>s сейчас, и вы можете просто сделать assert-was-call (или как это выглядит в Moq).

Другие вопросы по тегам