如何最小化来自亚马逊 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(); }
            }
        }
    }
    

    }