我目前正在尝试将消费者驱动的契约测试引入现有的 C# 代码库。 目前我只考虑测试messages!
在尝试为某些消息创建消费者测试时,我遇到了抽象类型的问题。
让我用一个例子来解释一下。
假设我们有以下消息:
public abstract record KeyValuePair(string Key);
public record IntegerKeyValuePair(string Key, int IntegerValue) : KeyValuePair(Key);
public record DoubleKeyValuePair(string Key, double DoubleValue) : KeyValuePair(Key);
public record Message(KeyValuePair[] KeyValuePairs);
现在我们编写以下消费者测试:
public class StackOverflowConsumerTest
{
private readonly IMessagePactBuilderV3 _pact;
public StackOverflowConsumerTest()
{
this._pact = Pact.V3(
"StackOverflowConsumer",
"StackOverflowProvider",
new PactConfig { PactDir = "./pacts/" })
.WithMessageInteractions();
}
[Fact]
public void ShouldReceiveExpectedMessage()
{
var message = new
{
KeyValuePairs = Match.MinType(
new
{
Key = Match.Type("SomeValue"),
DoubleValue = Match.Decimal(12.3)
}, 1)
};
_pact
.ExpectsToReceive(nameof(Message))
.WithJsonContent(message)
.Verify<Message>(msg =>
{
Assert.Equal(new DoubleKeyValuePair("SomeValue", 12.3), msg.KeyValuePairs.Single());
});
}
}
运行此 sonsumer 测试将会失败,因为我无法从 Pact.Net + Newtonsoft 生成的 JSON 表示中反序列化消息:
Newtonsoft.Json.JsonSerializationException: Could not create an instance of type PactNet.Learning.KeyValuePair. Type is an interface or abstract class and cannot be instantiated.
我通常会通过更改序列化器设置以包含序列化的实际类型来处理此类情况。然而,这里实际的对象从未真正传递给序列化器。相反,它接收一些动态类型结构,其中包含我用预期值定义的匹配器。 有没有一种方法可以使用我忽略的 Pact.Net 匹配器来处理抽象类型,或者这根本不可能?
解决我的问题的一个方法可能是“重构消息”,但这不是我现在能做的。
如果我正确理解 Pact.NET 库(并且我对此并不完全确定),那么有问题的反序列化应该发生在 ConfiguredMessageVerifier.MessageReified
如果是这种情况,您可以在 JsonSerializerSettings
初始化(您的第三个参数)中使用自定义
Pact.V3
指定 PactConfig.DefaultJsonSettings。
应该重载
withJsonContent
方法以允许传递自定义 JsonSerializerSettings
。
我对 .NET 不太熟悉,所以我不确定配置自定义
JsonSerializerSettings
是否足以正确序列化和反序列化您的抽象类型。据我所知,注入整个序列化/反序列化方法而不是依赖于Newtonsoft.Json
是不可能的。
编辑:这并不能解决问题,因为使用了
dynamic
对象(参见下面的答案)。这是一个如何解决它的理论方法(我还没有尝试过)。灵感来自这篇文章:https://stackoverflow.com/a/65261072/12256497
是否可以引入专门用于契约的记录并使用它们来代替动态对象?示意性地(我不知道 Matcher 类型是否正确):
public abstract record PactKeyValuePair(StringMatcher Key);
public record IntegerPactKeyValuePair(StringMatcher Key, IntMatcher IntegerValue) : PactKeyValuePair(Key);
public record DoublePactKeyValuePair(StringMatcher Key, DecimalMatcher DoubleValue) : PactKeyValuePair(Key);
public record PactMessage(PactKeyValuePair[] KeyValuePairs);
然后您可以使用执行以下操作的
ISerializationBinder
注册自定义 JsonSerializerSettings
:
PactKeyValuePair
方法中将 "WorkaroundKeyValuePair"
序列化为人工 $type(例如 BindToName
)KeyValuePair
方法中从人工 $type 反序列化为 BindToType
(因此是没有匹配器的原始类型)。