【问题标题】:Mocking (MOQ) passed parameter methods (WebAPI MVC Controller)模拟(MOQ)传递参数方法(WebAPI MVC控制器)
【发布时间】:2015-07-28 02:13:50
【问题描述】:

对于不知道此场景的技术名称,我深表歉意。我在嘲笑单元测试,这一切都很好。然而,在这部分代码中,我遇到了一个超出我的模拟知识的场景。基本上我有 MethodA 需要 3 个参数。其中一个参数作为另一个方法的输出传递。

当我单步执行作为参数传递的方法时

我的困难是传递的方法是在我的模拟对象之前执行的。现在它似乎是一个简单的解决方案......也模拟第二种方法......这就是我的知识下降的地方。我不知道如何将“第二个”方法模拟到测试上下文中。

我的控制器正在测试(当然是简化的):

public class OrderController : ApiController
{

    public OrderController(IRepositoryK repositoryk)
    {}

    public HttpResponseMessage NewOrder()
    {
       ...snip....
       string x = repositoryk.MethodA("stuff", "moreStuff", MethodB("junk"));

    }

    public string MethodB(string data)
    {
       using (var client = new HttpClient())
       {...make call to Google API...}
    }

}

我的测试:

[TestMethod]
public void AddOrder_CorrectResponse()
{

   private Mock<IRepositoryK> _repK = new Mock<IRepositoryK>();
   _repK.Setup(x => x.MethodA(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
   .Returns("Yippe");

  //of course I've left out all the controller buildup and execution stuff.

}

所以我真的不想深入研究 MethodB,但无论如何它似乎正在这样做。我做错了什么?

TIA


感谢您的回复。我完全明白你在说什么。我试图在重构之前获得一些测试覆盖率。那么有没有办法阻止 methodB 执行,让我的 repositoryK 模拟只返回我在设置中指定的内容。

【问题讨论】:

  • 您将无法轻松模拟 MethodB,因为它在同一个类中,而不是在可模拟的依赖项中
  • 您的方法 B 使用 Httpclient。您应该将其拉入它自己的存储库(google api)并通过 DI 将其传递给构造函数。为了测试一个类的功能,所有对外部资源(数据库、队列、Web 服务)的引用都应该被模拟出来并通过 DI 传入。你的 MethodB 应该使用这个 google api repo,它不应该关心它是真正的 google api repo,还是模拟版本
  • @GPGVM 是的,有一种方法可以阻止 MethodB 执行 - 请查看我的答案的第二部分以了解如何做到这一点

标签: c# asp.net-web-api moq


【解决方案1】:

你的代码不容易测试,因为它对 HttpClient 有很强的依赖。您已经很好地分离了存储库实现,但如果您想轻松测试代码,您还应该分离调用 Google API 的代码。我们的想法是有这样的东西:

// Add interfece for accessing Google API
public interface IGoogleClient
{
    string GetData(string data);
}

// Then implementation is identical to MethodB implementation:
public class GoogleClient : IGoogleClient
{
    public string GetData(string data)
    {
        using (var client = new HttpClient())
        {
        //...make call to Google API...
        }
    }
}

// Your controller should look like this:
public class OrderController : ApiController
{
    private readonly IRepositoryK repositoryk;
    private readonly IGoogleClient googleClient;

    public OrderController(IRepositoryK repositoryk, IGoogleClient googleClient)
    {
        this.googleClient = googleClient;
        this.repositoryk = repositoryk;
    }

    public HttpResponseMessage NewOrder()
    {
       //...snip....
       string x = repositoryk.MethodA("stuff", "moreStuff", MethodB("junk"));
    }

    public string MethodB(string data)
    {
        return googleClient.GetData(data);
    }
}

如果您有这样的设置,您可以轻松地模拟 IRepositoryKIGoogleClient

Mock<IRepositoryK> repK = new Mock<IRepositoryK>();
Mock<IGoogleClient> googleClient = new Mock<IGoogleClient>();
repK.Setup(x => x.MethodA(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns("Yippe");
googleClient.Setup(It.IsAny<string>()).Returns("something");
var controller = new OrderController(repK.Object, googleClient.Object);
// Test what you want on controller object

但是,如果您想保持代码紧密耦合,您可以模拟对 MethodB 的调用,只需稍作改动即可。

首先,您需要将方法MethodB 设为虚拟,这样它就可以在模拟中被覆盖:

public virtual string MethodB(string data)
{
    // your code
}

然后在您的测试中,而不是实例化控制器,实例化并使用您的控制器的模拟:

var repK = new Mock<IRepositoryK>();
// create mock and pass the same constructor parameters as actual object
var controllerMock = new Mock<OrderController>(repK.Object);
controllerMock.CallBase = true;
// mock MethodB method:
controllerMock.Setup(x => x.MethodB(It.IsAny<string>())).Returns("data");
// call the method on mock object
// instead of calling MethodB you will get a mocked result
var result = controllerMock.Object.NewOrder();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-25
    相关资源
    最近更新 更多