我有一个这样的设置,一个具体的类在我想要测试的方法中实例化。我想模仿这个具体的类,没有它执行里面的代码。因此,不应该抛出任何例外:
public class Executor
{
public bool ExecuteAction(ActionRequest request)
{
switch (request.ActionType)
{
case ActionType.Foo:
var a = new Foo();
return a.Execute(request);
case ActionType.Bar:
var b = new Bar();
return b.Execute(request);
}
return true;
}
}
public class Foo
{
public virtual bool Execute(ActionRequest request)
{
throw new NotImplementedException();
}
}
public class Bar
{
public virtual bool Execute(ActionRequest request)
{
throw new NotImplementedException();
}
}
我的NUnit测试如下所示:
[Test]
public void GivenARequestToFooShouldExecuteFoo()
{
var action = new Mock<Foo>();
action.Setup(x => x.Execute(It.IsAny<ActionRequest>())).Returns(true);
var sut = new Mock<Executor>();
sut.Object.ExecuteAction(new ActionRequest
{
ActionType = ActionType.Foo
});
}
[Test]
public void GivenARequestToBarShouldExecuteBar()
{
var action = new Mock<Bar>();
action.Setup(x => x.Execute(It.IsAny<ActionRequest>())).Returns(true);
var sut = new Mock<Executor>();
sut.Object.ExecuteAction(new ActionRequest
{
ActionType = ActionType.Bar
});
}
我摆弄了CallBase
,但它并没有把我带到任何地方。无论如何,我可以轻松地解决这个问题,而无需依赖注入这些类并添加接口?这可能只是使用Moq吗?
我目前唯一能想到的就是将Execute方法移到Executor类中并将它们重命名为ExecuteFoo()
和ExecuteBar()
,但是我有很多代码要移动它们必须是部分类(子类?)。
答案 0 :(得分:3)
问题不在于模拟方法,而在于创建具体类。 Foo
和Bar
的创建需要从Executor
中反转出来。它负责执行操作,而不是创建操作。创建此接口是为了处理创建。
public interface IActionCollection : IDictionary<ActionType, Func<IExecute>> {
}
将此视为工厂集合或创作策略集合。
为操作创建了一个通用界面。
public interface IExecute {
bool Execute(ActionRequest request);
}
public class Foo : IExecute {
public virtual bool Execute(ActionRequest request) {
throw new NotImplementedException();
}
}
public class Bar : IExecute {
public virtual bool Execute(ActionRequest request) {
throw new NotImplementedException();
}
}
Executor
被重构为使用依赖性反转。
public class Executor {
readonly IActionCollection factories;
public Executor(IActionCollection factories) {
this.factories = factories;
}
public bool ExecuteAction(ActionRequest request) {
if (factories.ContainsKey(request.ActionType)) {
var action = factories[request.ActionType]();
return action.Execute(request);
}
return false;
}
}
完成重构后,Executor可以通过虚假动作进行测试。
public void GivenARequestToFooShouldExecuteFoo() {
//Arrange
var expected = true;
var key = ActionType.Foo;
var action = new Mock<Foo>();
action.Setup(x => x.Execute(It.IsAny<ActionRequest>())).Returns(expected);
var actions = new Mock<IActionCollection>();
actions.Setup(_ => _[key]).Returns(() => { return () => action.Object; });
actions.Setup(_ => _.ContainsKey(key)).Returns(true);
var sut = new Executor(actions.Object);
var request = new ActionRequest {
ActionType = ActionType.Foo
};
//Act
var actual = sut.ExecuteAction(request);
//Assert
Assert.AreEqual(expected, actual);
}
工厂集合的生产实现可能如下所示
public class ActionCollection : Dictionary<ActionType, Func<IExecute>>, IActionCollection {
public ActionCollection()
: base() {
}
}
并根据您的具体类型进行相应配置。
var factories = ActionCollection();
factories[ActionType.Foo] = () => new Foo();
factories[ActionType.Bar] = () => new Bar();