Как мне издеваться над 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
The<IMessageRouters>()
.WhenToldTo(x => x.Where(router => router.CanRoute(Param.IsAny<ProcessSkuCostUpdated>())))
.Return(new List<IMessageRouter> {Router});
Вышесказанное не имеет никакого отношения к Where, выполняемому во время выполнения, и не может быть, так как func компилируется в приватный метод во время компиляции. Похоже, издеваться над функцией, мне нужно подтолкнуть его к интерфейсу. Хотя пахнет, так как я подталкиваю внутреннее поведение исключительно для тестирования.
2 ответа
Я вижу две проблемы с вашим тестовым кодом:
- Выражение, которое вы используете для настройки
Where()
позвонить наIMessageRouters
слишком явно. Не должно заботиться о том, какая именно функция передается. - Вы проверяете,
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).