如何最小化来自亚马逊 MWS 的响应以进行单元测试
How to moq responses from Amazon MWS for unit testing
上下文
我正在对调用亚马逊 MWS API(使用 MWSClientCsRuntime)中的 ListMatchingProducts
操作的 C# .Net class 进行单元测试。
问题
亚马逊 MWS API 是一个移动目标,产品数据一直在变化,所以我希望能够对 ListMatchingProductsResponse
对象进行最小起订量 API returns。我可以使用 MWS 暂存器获取 API 响应并将它们存储在 xml 文件中。但是,在单元测试中,我需要将这些文件中的数据强制转换为 ListMatchingProductsResponse
对象。
问题
如何将此 xml 数据加载到 ListMatchingProductsResponse
对象中? (我注意到该对象有一个 ReadFragmentsFrom
方法,但我看不出它是如何使用的)。
代码
[TestClass]
public class PossibleAmazonProductMatchesTests
{
string testDataDirectory = Directory.GetParent(Directory.GetCurrentDirectory()).Parent.FullName + @"\Test data";
[TestMethod]
public void FindSpanners()
{
// Arrange
ListMatchingProductsRequest request = new ListMatchingProductsRequest("secret key", "market id", "spanner");
ListMatchingProductsResult result = new ListMatchingProductsResult();
ListMatchingProductsResponse response = new ListMatchingProductsResponse();
string xmlString = File.ReadAllText(this.testDataDirectory + @"\Spanners Response.xml");
// *** The issue - How do I coerce xmlString into response? ***
var client = new Mock<MarketplaceWebServiceProductsClient>();
client.Setup(c => c.ListMatchingProducts(request)).Returns(response);
// Act
// This is the method being tested. It calls ListMatchingProducts which is being mocked.
PossibleAmazonProductMatches possibleAmazonProductMatches = new PossibleAmazonProductMatches("spanners", client);
// Assert
Assert.IsTrue(possibleAmazonProductMatches.SpannersFound == true);
}
}
C# client library 已经存在好几年了,开箱即用。您不必处理任何 XML,包括反序列化。如果您不想使用他们的代码但想看看它是如何完成的,请将其打开到 Visual Studio 并复制您想要的部分。我已经使用了所有的 C# 库,它们都非常好。你找到你想要的操作并取消注释他们的代码和 运行。我相信他们有示例 XML 数据,其中包含您将从各种操作中获得的所有响应。
这看起来像是一个简单的案例,必须读取 XML 文件,然后将其从 XML 反序列化为所需的对象类型。
更好的是,您可以将其抽象为执行所需行为的服务,而无需将代码与实现问题紧密耦合。
将 MWS 视为第 3 部分服务,并将其包装在您可以完全控制的抽象之后。这样您就可以在测试时配置所需的行为。
基于@Nkosi 和@ScottG 的出色回应,我现在有了一个可行的解决方案,尽管有几个要点需要注意,但事实证明它非常简单。所以单元测试代码是:
[TestClass]
public class PossibleAmazonProductMatchesTests
{
[TestMethod]
public void Test1()
{
// Arrange
var moqClient = new MarketplaceWebServiceProductsMock();
// Act
PossibleAmazonProductMatches possibleAmazonProductMatches = new PossibleAmazonProductMatches("spanners", moqClient);
// Assert
Assert.IsTrue(possibleAmazonProductMatches.PossibleProductList.Count == 10);
}
}
..就是这样。能再简单点吗!
为了抽象,被测对象 (PossibleAmazonProductMatches) 具有此构造函数:
public PossibleAmazonProductMatches(string searchTerm, MarketplaceWebServiceProducts.MarketplaceWebServiceProducts client)
{
// Some processing
}
需要注意的重点是:
- MarketplaceWebServiceProducts 实际上是一个接口,尽管它不遵循 ISomething 命名约定。
- MarketplaceWebServiceProducts 也用作 命名空间名称 因此需要在 PossibleAmazonProductMatches 构造函数中使用双重 MarketplaceWebServiceProducts.MarketplaceWebServiceProducts 语法。
- MarketplaceWebServiceProductsMock 包含在 MWS 包中,因此无需编写任何代码。
默认情况下,MarketplaceWebServiceProductsMock 从一个固定的 xml 模板文件中读取并使用它来构建您的测试响应。您可以根据需要编辑此文件。我实际上想创建自己的 xml 文件,这些文件来自 MWS 暂存器,并希望将这些文件存储在更方便的位置。我以为我可以从 MarketplaceWebServiceProductsMock 继承并覆盖相关代码来执行此操作,但事实证明这被隐藏在一个私有方法中。因此,我只是复制了 MarketplaceWebServiceProductsMock 并对其进行了修改以满足我的需要。因此我的模拟现在看起来像这样:
using MarketplaceWebServiceProducts.Model;
using System;
using System.IO;
using MWSClientCsRuntime;
namespace AmazonMWS.Tests
{
public class MyMWSMock : MarketplaceWebServiceProducts.MarketplaceWebServiceProducts
{
// Definitions of most methods removed for brevity. They all match the pattern of ListMatchingProductsResponse.
public ListMatchingProductsResponse ListMatchingProducts(ListMatchingProductsRequest request)
{
return newResponse<ListMatchingProductsResponse>();
}
private T newResponse<T>() where T : IMWSResponse
{
FileStream xmlIn = File.Open("D:\MyTestDataFolder\Test1.xml", FileMode.Open);
try
{
StreamReader xmlInReader = new StreamReader(xmlIn);
string xmlStr = xmlInReader.ReadToEnd();
MwsXmlReader reader = new MwsXmlReader(xmlStr);
T obj = (T)Activator.CreateInstance(typeof(T));
obj.ReadFragmentFrom(reader);
obj.ResponseHeaderMetadata = new ResponseHeaderMetadata("mockRequestId", "A,B,C", "mockTimestamp", 0d, 0d, new DateTime());
return obj;
}
catch (Exception e)
{
throw MwsUtil.Wrap(e);
}
finally
{
if (xmlIn != null) { xmlIn.Close(); }
}
}
}
}
上下文
我正在对调用亚马逊 MWS API(使用 MWSClientCsRuntime)中的 ListMatchingProducts
操作的 C# .Net class 进行单元测试。
问题
亚马逊 MWS API 是一个移动目标,产品数据一直在变化,所以我希望能够对 ListMatchingProductsResponse
对象进行最小起订量 API returns。我可以使用 MWS 暂存器获取 API 响应并将它们存储在 xml 文件中。但是,在单元测试中,我需要将这些文件中的数据强制转换为 ListMatchingProductsResponse
对象。
问题
如何将此 xml 数据加载到 ListMatchingProductsResponse
对象中? (我注意到该对象有一个 ReadFragmentsFrom
方法,但我看不出它是如何使用的)。
代码
[TestClass]
public class PossibleAmazonProductMatchesTests
{
string testDataDirectory = Directory.GetParent(Directory.GetCurrentDirectory()).Parent.FullName + @"\Test data";
[TestMethod]
public void FindSpanners()
{
// Arrange
ListMatchingProductsRequest request = new ListMatchingProductsRequest("secret key", "market id", "spanner");
ListMatchingProductsResult result = new ListMatchingProductsResult();
ListMatchingProductsResponse response = new ListMatchingProductsResponse();
string xmlString = File.ReadAllText(this.testDataDirectory + @"\Spanners Response.xml");
// *** The issue - How do I coerce xmlString into response? ***
var client = new Mock<MarketplaceWebServiceProductsClient>();
client.Setup(c => c.ListMatchingProducts(request)).Returns(response);
// Act
// This is the method being tested. It calls ListMatchingProducts which is being mocked.
PossibleAmazonProductMatches possibleAmazonProductMatches = new PossibleAmazonProductMatches("spanners", client);
// Assert
Assert.IsTrue(possibleAmazonProductMatches.SpannersFound == true);
}
}
C# client library 已经存在好几年了,开箱即用。您不必处理任何 XML,包括反序列化。如果您不想使用他们的代码但想看看它是如何完成的,请将其打开到 Visual Studio 并复制您想要的部分。我已经使用了所有的 C# 库,它们都非常好。你找到你想要的操作并取消注释他们的代码和 运行。我相信他们有示例 XML 数据,其中包含您将从各种操作中获得的所有响应。
这看起来像是一个简单的案例,必须读取 XML 文件,然后将其从 XML 反序列化为所需的对象类型。
更好的是,您可以将其抽象为执行所需行为的服务,而无需将代码与实现问题紧密耦合。
将 MWS 视为第 3 部分服务,并将其包装在您可以完全控制的抽象之后。这样您就可以在测试时配置所需的行为。
基于@Nkosi 和@ScottG 的出色回应,我现在有了一个可行的解决方案,尽管有几个要点需要注意,但事实证明它非常简单。所以单元测试代码是:
[TestClass]
public class PossibleAmazonProductMatchesTests
{
[TestMethod]
public void Test1()
{
// Arrange
var moqClient = new MarketplaceWebServiceProductsMock();
// Act
PossibleAmazonProductMatches possibleAmazonProductMatches = new PossibleAmazonProductMatches("spanners", moqClient);
// Assert
Assert.IsTrue(possibleAmazonProductMatches.PossibleProductList.Count == 10);
}
}
..就是这样。能再简单点吗!
为了抽象,被测对象 (PossibleAmazonProductMatches) 具有此构造函数:
public PossibleAmazonProductMatches(string searchTerm, MarketplaceWebServiceProducts.MarketplaceWebServiceProducts client)
{
// Some processing
}
需要注意的重点是:
- MarketplaceWebServiceProducts 实际上是一个接口,尽管它不遵循 ISomething 命名约定。
- MarketplaceWebServiceProducts 也用作 命名空间名称 因此需要在 PossibleAmazonProductMatches 构造函数中使用双重 MarketplaceWebServiceProducts.MarketplaceWebServiceProducts 语法。
- MarketplaceWebServiceProductsMock 包含在 MWS 包中,因此无需编写任何代码。
默认情况下,MarketplaceWebServiceProductsMock 从一个固定的 xml 模板文件中读取并使用它来构建您的测试响应。您可以根据需要编辑此文件。我实际上想创建自己的 xml 文件,这些文件来自 MWS 暂存器,并希望将这些文件存储在更方便的位置。我以为我可以从 MarketplaceWebServiceProductsMock 继承并覆盖相关代码来执行此操作,但事实证明这被隐藏在一个私有方法中。因此,我只是复制了 MarketplaceWebServiceProductsMock 并对其进行了修改以满足我的需要。因此我的模拟现在看起来像这样:
using MarketplaceWebServiceProducts.Model; using System; using System.IO; using MWSClientCsRuntime; namespace AmazonMWS.Tests { public class MyMWSMock : MarketplaceWebServiceProducts.MarketplaceWebServiceProducts { // Definitions of most methods removed for brevity. They all match the pattern of ListMatchingProductsResponse. public ListMatchingProductsResponse ListMatchingProducts(ListMatchingProductsRequest request) { return newResponse<ListMatchingProductsResponse>(); } private T newResponse<T>() where T : IMWSResponse { FileStream xmlIn = File.Open("D:\MyTestDataFolder\Test1.xml", FileMode.Open); try { StreamReader xmlInReader = new StreamReader(xmlIn); string xmlStr = xmlInReader.ReadToEnd(); MwsXmlReader reader = new MwsXmlReader(xmlStr); T obj = (T)Activator.CreateInstance(typeof(T)); obj.ReadFragmentFrom(reader); obj.ResponseHeaderMetadata = new ResponseHeaderMetadata("mockRequestId", "A,B,C", "mockTimestamp", 0d, 0d, new DateTime()); return obj; } catch (Exception e) { throw MwsUtil.Wrap(e); } finally { if (xmlIn != null) { xmlIn.Close(); } } } }
}