如何正确对含SerializationInfo的自定义异常进行单元测试?
测试自定义异常的受保护序列化构造函数
要测试这个受保护的SerializationInfo+StreamingContext构造函数,核心思路是通过反射调用构造函数,模拟.NET序列化机制所需的参数环境,具体可分两种测试场景:
1. 直接测试构造函数的初始化逻辑
因为构造函数是protected的无法直接调用,所以用反射获取构造函数实例,手动填充SerializationInfo的必要字段,再验证构造出的异常属性是否符合预期:
using System; using System.Reflection; using System.Runtime.Serialization; using Xunit; // 以xUnit为例,NUnit等框架写法类似 public class DatabaseVersionNotReadableExceptionTests { [Fact] public void ProtectedSerializationConstructor_InitializesExpectedProperties() { // 获取受保护的构造函数 var ctor = typeof(DatabaseVersionNotReadableException) .GetConstructor( BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(SerializationInfo), typeof(StreamingContext) }, null); // 确保构造函数存在 Assert.NotNull(ctor); // 构造SerializationInfo:需填充Exception基类序列化依赖的字段 var info = new SerializationInfo(typeof(DatabaseVersionNotReadableException), new FormatterConverter()); // 按Exception的序列化要求添加必要属性 info.AddValue("Message", "Database Version not found or not readable: v1.0.0"); info.AddValue("InnerException", new InvalidOperationException("Mock inner error")); info.AddValue("Source", "TestAssembly"); info.AddValue("HelpLink", "test-help-url"); // 创建默认的StreamingContext var context = new StreamingContext(StreamingContextStates.All); // 调用构造函数实例化异常 var exception = (DatabaseVersionNotReadableException)ctor.Invoke(new object[] { info, context }); // 验证属性是否正确初始化 Assert.Equal("Database Version not found or not readable: v1.0.0", exception.Message); Assert.IsType<InvalidOperationException>(exception.InnerException); Assert.Equal("Mock inner error", exception.InnerException.Message); Assert.Equal("TestAssembly", exception.Source); Assert.Equal("test-help-url", exception.HelpLink); } }
2. 测试完整的序列化/反序列化流程
更贴近真实场景的测试:先序列化一个正常创建的异常,再用受保护构造函数反序列化,验证异常状态是否完整还原:
[Fact] public void Exception_SerializesAndDeserializesWithoutLoss() { // 1. 创建一个真实的异常实例 var originalEx = new DatabaseVersionNotReadableException("v2.0.0", new ArgumentNullException("versionParam")); // 2. 模拟序列化过程:调用GetObjectData获取序列化信息 var serializationInfo = new SerializationInfo(typeof(DatabaseVersionNotReadableException), new FormatterConverter()); originalEx.GetObjectData(serializationInfo, new StreamingContext(StreamingContextStates.All)); // 3. 通过反射调用受保护构造函数反序列化 var ctor = typeof(DatabaseVersionNotReadableException) .GetConstructor( BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(SerializationInfo), typeof(StreamingContext) }, null); var deserializedEx = (DatabaseVersionNotReadableException)ctor.Invoke(new object[] { serializationInfo, new StreamingContext(StreamingContextStates.All) }); // 4. 验证序列化前后状态一致 Assert.Equal(originalEx.Message, deserializedEx.Message); Assert.IsType<ArgumentNullException>(deserializedEx.InnerException); Assert.Equal("versionParam", ((ArgumentNullException)deserializedEx.InnerException).ParamName); }
关键说明:
SerializationInfo需要填充Exception基类序列化依赖的字段(比如Message、InnerException等),这些是.NET内置序列化机制的约定字段,缺少可能导致构造函数抛出异常。StreamingContext通常传默认的StreamingContextStates.All即可,大部分场景下这个参数不会影响异常的初始化逻辑。
内容的提问来源于stack exchange,提问作者PassionateDeveloper
相关产品推荐
相关产品推荐

