如何正确测试ICollection类型属性是否返回真正的只读集合?
你说得对,选项1的断言完全不靠谱——毕竟List<string>确实实现了IReadOnlyCollection<string>,但它本质还是可变的,客户端拿到之后转成List<string>就能随便修改。要测试返回的是真正不可被客户端修改的集合,得从「行为」入手,而不是只看类型。
下面是两种靠谱的测试方式:
方法1:尝试修改集合,验证会抛出异常
这是最直接且有效的方式,因为真正的只读集合(比如ReadOnlyCollection<string>、ImmutableArray<string>或者用List.AsReadOnly()包装的实例)在尝试执行添加/移除/清除操作时,会抛出NotSupportedException。
示例测试代码(以NUnit为例):
[Test] public void MyCollection_ReturnsTrulyReadOnlyCollection() { // Arrange var model = new Model(); // Act & Assert Assert.Throws<NotSupportedException>(() => model.MyCollection.Add("test")); Assert.Throws<NotSupportedException>(() => model.MyCollection.Remove("any")); Assert.Throws<NotSupportedException>(() => model.MyCollection.Clear()); }
因为你的MyCollection声明为ICollection<string>,这三个操作是接口定义的标准方法,直接调用即可。不管内部用的是什么只读实现,只要客户端没法修改,测试就会通过。
方法2:验证返回的集合是不可变的具体类型(可选辅助验证)
如果你明确知道内部应该返回某个特定的只读类型(比如ReadOnlyCollection<string>或者ImmutableList<string>),也可以加上类型断言,但这不能替代行为测试——因为未来如果换成另一种合规的只读实现,类型断言会失败,但行为依然符合要求。不过可以作为辅助验证:
Assert.That(model.MyCollection, Is.TypeOf<ReadOnlyCollection<string>>());
额外提醒:确保类内部的实现正确
要让测试通过,你的Model类内部必须确保返回的是包装后的只读实例,而不是直接返回可变集合的引用。比如正确的实现应该是这样:
public class Model { private readonly List<string> _internalList = new(); public ICollection<string> MyCollection => _internalList.AsReadOnly(); }
这样返回的ReadOnlyCollection<string>不仅在尝试修改时会抛异常,也无法被强制转成List<string>来绕过只读限制。
内容的提问来源于stack exchange,提问作者Ilya Loskutov

