提问人:Rocker2982 提问时间:11/9/2023 更新时间:11/9/2023 访问量:59
基于最小起订量框架的Web服务方法的模拟请求和响应
Mock Request and Response of Web service method with Moq Framework
问:
总结:我希望能够模拟对远程服务器进行调用的 SOAP Web 方法的请求和响应。我希望能够测试此 SOAP Web 方法 CustomerCareResult 的整个请求和响应功能,而无需进行远程服务器调用。这意味着我将不得不使用嘲笑。我想使用 C#.NET 中的 Moq 框架模拟 CustomerCareResult 的传入请求和响应输出。我是用 C# 编写模拟测试的全新手,弄清楚这一点对我来说非常令人沮丧。任何帮助将不胜感激。
注意:这里有一些伪代码,因为整体代码非常复杂,我只想大致了解它在做什么,而不添加不必要的细节。
我有一个包含SOAP WebMethod和其他2个方法的C#.NET类,如下所示:
public class TransactionService : System.Web.Services.WebService,
TransactionServiceNS.ITransactionServiceSoap
{
//This is a SOAP webservice method
[WebMethod()]
public CustomerPutResponse CustomerCareResult(CustomerRequest request)
{
//The incoming request parameter of type 'CustomerRequest'
//contains the fields: Name, Address, City, State, and AccountNumber
//Call the ApiCreateRequestObj method, passing in the request parameter. This makes
//a call to a remote server:
var apiRequestObj = ApiCreateRequestObj(request);
//Call the ApiCreateResponseObj method, passing in apiRequestObj, this makes
//a call to a remote server:
var apiResponseObj = ApiCreateResponseObj(apiRequestObj );
return apiResponseObj;
}
private CustomerPutRequest ApiCreateRequestObj(CustomerRequest req)
{
// PSEUDOCODE:
//Makes a remote server call, does some processing on the
//CustomerRequest req parameter, and returns a "CustomerPutRequest"
//object containing all the fields of the req object
//So after processing:
return new CustomerPutRequest{Name = _req.Name, Address = _req.Address,
City = _req.City, State = _req.State}
}
private CustomerPutResponse ApiCreateResponseObj(CustomerPutRequest cpReq)
{
// PSEUDOCODE:
//Makes a remote server call, does some processing on the cpReq object and returns a
//"CustomerPutResponse" object containing the response data
//So after processing:
return new CustomerPutResponse{/*contains response data fields*/}
}
}
如您所见,SOAP Web 方法 CustomerCareResult 接受一个“CustomerRequest”参数,然后调用一个进行远程数据库调用的方法 ApiCreateRequestObject()。这将执行一些处理并返回一个“CustomerPutRequest”对象,该对象包含传入的 CustomerRequest 对象的字段。
然后,我将此 CustomerPutRequest 对象传递给 ApiCreateResponseObject() 方法,该方法又进行远程服务器调用,自行进行一些处理,然后返回包含响应数据的“CustomerPutResponse”对象,该对象由 Web 方法 CustomerCareResult 返回。
我想使用 C#.NET 中的 Moq 框架模拟 Web 方法 CustomerCareResult 的传入请求和响应输出。我是用 C# 编写模拟测试的全新手,弄清楚这一点对我来说非常令人沮丧。任何帮助将不胜感激。
答:
为了在不对外部服务进行实际调用的情况下测试类 TransactionService,请确保在伪调用中使用的服务位于接口后面。如果不是这种情况,您可能需要重构源代码以使其可测试。
public class TransactionService
{
private readonly IDatabaseService _databaseService;
public TransactionService(IDatabaseService databaseService)
{
_databaseService = databaseService;
}
public CustomerPutResponse CustomerCareResult(CustomerRequest request)
{
var apiRequestObj = ApiCreateRequestObj(request);
var apiResponseObj = ApiCreateResponseObj(apiRequestObj);
return apiResponseObj;
}
private CustomerPutRequest ApiCreateRequestObj(CustomerRequest req)
{
return _databaseService.createPutRequest(req);
}
private CustomerPutResponse ApiCreateResponseObj(CustomerPutRequest resp)
{
return _databaseService.createPutResponse(resp);
}
}
如果要测试 TransactionService 内部的逻辑,而不是外部 DatabaseService 内部的逻辑,则应使用 TransactionService 中的 IDatabaseService,并从测试中模拟接口 IDatabaseService。真正的实现 DatabaseService 只会在您的应用程序中运行,但您的测试不会通过它:
public interface IDatabaseService
{
public CustomerPutRequest createPutRequest(CustomerRequest request);
public CustomerPutResponse createPutResponse(CustomerPutRequest putRequest);
}
public class DatabaseService : IDatabaseService
{
public CustomerPutRequest createPutRequest(CustomerRequest request)
{
//PSEUDOCODE: Real code for your real service that returns a customerPutRequest
}
public CustomerPutResponse createPutResponse(CustomerPutRequest putRequest)
{
//PSEUDOCODE: Real code for your real service that returns a customerPutResponse
}
}
最后,在测试类中(在此示例中使用 xUnit),应测试类的公共方法,在本例中为 CustomerCareResult 方法:
public class TransactionServiceTests
{
[Fact]
public void CustomerCareResult_UponCalled_ReturnsExpectedResponse()
{
// Arrange (instantiate input parameters, mocks and the object to test (sut))
var customerRequest = new CustomerRequest
{
Name = "Joe Biden",
Address = "White House",
City = "Washington DC",
State = "Columbia",
AccountNumber = "666"
};
//Create the mock for the external service we don't really want to call
var dataServiceMock = new Mock<IDatabaseService>();
//Set up the mock to behave as we would expect the real class to behave when calling its methods.
//In this case, to return the expected value whenever the external IDatabaseService.createPutRequest is called
dataServiceMock .Setup(m => m.createPutRequest(It.IsAny<CustomerRequest>()))
.Returns(new CustomerPutRequest
{
Name = "Joe Biden",
Address = "White House",
City = "Washington DC",
State = "Columbia"
});
dataServiceMock.Setup(m => m.createPutResponse(It.IsAny<CustomerPutRequest>()))
.Returns(new CustomerPutResponse
{
Data = "this is the mocked data I expect my real external service to return"
});
var sut = new TransactionService(dataServiceMock.Object);
// Act (call a method on the sut)
sut.CustomerCareResult(customerRequest);
// Assert (verify that each of the 2 the methods in the IDatabaseService mock have been called exactly once)
dataServiceMock.Verify(m => m.createPutRequest(It.IsAny<CustomerRequest>()), Times.Once());
dataServiceMock.Verify(m => m.createPutResponse(It.IsAny<CustomerPutRequest>()), Times.Once());
}
}
在测试方法中,使用 Moq 为服务 IDatabaseService 创建 2 个模拟。在设置部分,我们告诉 Moq,当 TransactionService 调用 _databaseService.createPutRequest(req) 和 _databaseService.createPutResponse(resp) 时,由于调用公共方法 CustomerCareResult,我们希望模拟如何响应。
在测试的最后一部分,我们断言每个方法都调用了一次。当然,如果 CustomerCareResult 中有更复杂的逻辑,则根据请求的信息,可能会为同一方法 CustomerCareResult 编写更多具有不同断言的测试。
评论
这可能取决于您如何进行远程服务器调用。例如,我使用的一个项目 - 它没有接口(通常你模拟接口而不是实际实现),所以我进一步将其抽象为提供等,然后使用下面的实现。然后我嘲笑我创造的。HttpClient
IHttpClientService
.Get()
.Post()
HttpClient
IHttpClientService
因此,在您的问题中,是您的测试范围,实际代码取决于可以向外部服务发出请求的东西(在您的代码示例中省略了)。这就是你想嘲笑的。TransactionService
当然,第 3 点可能表明另一个问题:通常不应该为了单元测试而更改代码。但在这种情况下,提供 的抽象可以帮助使 HTTP 请求更容易,而且因为它本身有点历史,HttpClient 的实现阻止了单元测试,所以在考虑之后,我认为用于发出 HTTP 请求的抽象对于我的项目来说是可以接受的。HttpClient
评论