在对依赖它的标记扩展进行单元测试时如何设置 StaticResourceExtension?
How can I setup StaticResourceExtension when unit testing a markup extension that relies on it?
我写了一个自定义标记扩展:CoalesceResourceExtension
[MarkupExtensionReturnType(typeof(object))]
public class CoalesceResourceExtension : MarkupExtension
{
public CoalesceResourceExtension(string resources)
{
this.Resources = resources;
}
public string Separator { get; set; }
[ConstructorArgument("resources")]
public string Resources { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (Resources != null)
{
foreach (string resourceName in Resources.Split(new[] { Separator }, StringSplitOptions.RemoveEmptyEntries))
{
try
{
if (new StaticResourceExtension(resourceName).ProvideValue(serviceProvider) is object resource)
{
return resource;
}
}
catch { }
}
}
return null;
}
}
但是当我进行单元测试时 CoalesceResourceExtension
,我似乎无法找到一种方法来设置 StaticResourceExtension
以使其找到所需的模拟资源。有什么方法可以让 StaticResourceExtension
找到我模拟的资源吗?
我 运行 遇到了与使用 Application.Current.TryFindResource()
.
的 ValueConverter
/MarkupExtension
类似的问题
我创建了一个名为 IApplicationResourceResolver
的 interface
,其中包含 TryFindResource
的方法定义。
然后我创建了一个名为 ApplicationResourceResolver
的接口实现,它使用 Application.Current.TryFindResource()
.
在我的 converter/extension 我有一个 public 属性:
public IApplicationResourceResolver Resolver {get; internal set; }
并且在我的构造函数中,我设置了我的默认实现:
public StringToResourceConverter()
{
Resolver = new ApplicationResourceResolver();
}
现在,在我的单元测试中,我可以将 Resolver
设置为模拟版本来进行测试!
您应该能够使用此模式来解决您的问题。希望对您有所帮助。
StaticResourceExtension.ProvideValue(IServiceProvider)
的行为可以通过模拟由 CoalesceResourceExtension
传递给 StaticResourceExtension
的 IServiceProvider
来控制,但您还需要模拟所有服务和它使用的服务中的对象:IAmbientProvider
、IXamlSchemaContextProvider
、XamlSchemaContext
、ResourceDictionary
、XamlType
、PropertyInfo
和 XamlMember
。我通过观察 StaticResourceExtension
采取的路径来构建这些模拟,方法是使模拟严格并观察他们在遇到未设置的成员时抛出的异常,并查看 StaticResourceExtension
'源代码。
这是一个为传入的每个需要的键 + 值对构建整个模拟 IServiceProvider
的方法:
private IServiceProvider MockIServiceProviderForStaticResourceExtension(params KeyValuePair<object, object>[] resources)
{
Mock<IServiceProvider> serviceProviderMock = new Mock<IServiceProvider>(MockBehavior.Strict);
Mock<IXamlSchemaContextProvider> xamlSchemaContextProviderMock = new Mock<IXamlSchemaContextProvider>(MockBehavior.Strict);
Mock<IAmbientProvider> ambientProviderMock = new Mock<IAmbientProvider>(MockBehavior.Strict);
Mock<XamlSchemaContext> xamlSchemaContextMock = new Mock<XamlSchemaContext>(MockBehavior.Strict);
xamlSchemaContextMock.Setup(xsc => xsc.GetXamlType(It.IsAny<Type>())).Returns((Func<Type, XamlType>)(t => GetXamlTypeForType(t, xamlSchemaContextMock.Object)));
ambientProviderMock.Setup(ap => ap.GetAllAmbientValues(null, false, It.IsAny<IEnumerable<XamlType>>(), It.IsAny<XamlMember[]>())).Returns((Func<IEnumerable<XamlType>, bool, IEnumerable<XamlType>, XamlMember[], IEnumerable<AmbientPropertyValue>>)GetAllAmbientValuesImplementation);
xamlSchemaContextProviderMock.Setup(xscp => xscp.SchemaContext).Returns(xamlSchemaContextMock.Object);
serviceProviderMock.Setup(s => s.GetService(typeof(IXamlSchemaContextProvider))).Returns(xamlSchemaContextProviderMock.Object);
serviceProviderMock.Setup(s => s.GetService(typeof(IAmbientProvider))).Returns(ambientProviderMock.Object);
serviceProviderMock.Setup(s => s.GetService(It.IsNotIn(typeof(IXamlSchemaContextProvider), typeof(IAmbientProvider)))).Returns(null);
return serviceProviderMock.Object;
IEnumerable<AmbientPropertyValue> GetAllAmbientValuesImplementation(IEnumerable<XamlType> ceilingTypes, bool searchLiveStackOnly, IEnumerable<XamlType> types, params XamlMember[] properties)
{
Mock<ResourceDictionary> resourceDictionaryMock = new Mock<ResourceDictionary>(MockBehavior.Strict);
resourceDictionaryMock.Protected().Setup("OnGettingValue", false, ItExpr.Is<object>(o => resources.Any(kvp => kvp.Key.Equals(o))), ItExpr.Ref<object>.IsAny, ItExpr.Ref<bool>.IsAny).CallBase();
foreach (KeyValuePair<object, object> kvp in resources)
{
resourceDictionaryMock.Object.Add(kvp.Key, kvp.Value);
}
Mock<AmbientPropertyValue> ambientPropertyValueMock = new Mock<AmbientPropertyValue>(MockBehavior.Strict, null, resourceDictionaryMock.Object);
return new List<AmbientPropertyValue> { ambientPropertyValueMock.Object };
}
XamlType GetXamlTypeForType(Type t, XamlSchemaContext xamlSchemaContext)
{
Mock<XamlType> xamlTypeMock = new Mock<XamlType>(MockBehavior.Strict, t, xamlSchemaContext);
xamlTypeMock.Protected().Setup<XamlMember>("LookupMember", true, "Resources", false).Returns((Func<string, bool, XamlMember>)LookupMemberImplementation);
xamlTypeMock.Protected().Setup<XamlMember>("LookupMember", true, "BasedOn", false).Returns((Func<string, bool, XamlMember>)LookupMemberImplementation);
xamlTypeMock.Setup(xt => xt.ToString()).CallBase();
return xamlTypeMock.Object;
XamlMember LookupMemberImplementation(string name, bool skipReadOnlyCheck)
{
Mock<PropertyInfo> propertyInfoMock = new Mock<PropertyInfo>(MockBehavior.Strict);
propertyInfoMock.Setup(pi => pi.Name).Returns(name);
propertyInfoMock.Setup(pi => pi.DeclaringType).Returns(t);
Mock<XamlMember> xamlMemberMock = new Mock<XamlMember>(MockBehavior.Strict, propertyInfoMock.Object, xamlSchemaContext);
xamlMemberMock.Setup(xm => xm.ToString()).CallBase();
return xamlMemberMock.Object;
}
}
}
我写了一个自定义标记扩展:CoalesceResourceExtension
[MarkupExtensionReturnType(typeof(object))]
public class CoalesceResourceExtension : MarkupExtension
{
public CoalesceResourceExtension(string resources)
{
this.Resources = resources;
}
public string Separator { get; set; }
[ConstructorArgument("resources")]
public string Resources { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (Resources != null)
{
foreach (string resourceName in Resources.Split(new[] { Separator }, StringSplitOptions.RemoveEmptyEntries))
{
try
{
if (new StaticResourceExtension(resourceName).ProvideValue(serviceProvider) is object resource)
{
return resource;
}
}
catch { }
}
}
return null;
}
}
但是当我进行单元测试时 CoalesceResourceExtension
,我似乎无法找到一种方法来设置 StaticResourceExtension
以使其找到所需的模拟资源。有什么方法可以让 StaticResourceExtension
找到我模拟的资源吗?
我 运行 遇到了与使用 Application.Current.TryFindResource()
.
ValueConverter
/MarkupExtension
类似的问题
我创建了一个名为 IApplicationResourceResolver
的 interface
,其中包含 TryFindResource
的方法定义。
然后我创建了一个名为 ApplicationResourceResolver
的接口实现,它使用 Application.Current.TryFindResource()
.
在我的 converter/extension 我有一个 public 属性:
public IApplicationResourceResolver Resolver {get; internal set; }
并且在我的构造函数中,我设置了我的默认实现:
public StringToResourceConverter()
{
Resolver = new ApplicationResourceResolver();
}
现在,在我的单元测试中,我可以将 Resolver
设置为模拟版本来进行测试!
您应该能够使用此模式来解决您的问题。希望对您有所帮助。
StaticResourceExtension.ProvideValue(IServiceProvider)
的行为可以通过模拟由 CoalesceResourceExtension
传递给 StaticResourceExtension
的 IServiceProvider
来控制,但您还需要模拟所有服务和它使用的服务中的对象:IAmbientProvider
、IXamlSchemaContextProvider
、XamlSchemaContext
、ResourceDictionary
、XamlType
、PropertyInfo
和 XamlMember
。我通过观察 StaticResourceExtension
采取的路径来构建这些模拟,方法是使模拟严格并观察他们在遇到未设置的成员时抛出的异常,并查看 StaticResourceExtension
'源代码。
这是一个为传入的每个需要的键 + 值对构建整个模拟 IServiceProvider
的方法:
private IServiceProvider MockIServiceProviderForStaticResourceExtension(params KeyValuePair<object, object>[] resources)
{
Mock<IServiceProvider> serviceProviderMock = new Mock<IServiceProvider>(MockBehavior.Strict);
Mock<IXamlSchemaContextProvider> xamlSchemaContextProviderMock = new Mock<IXamlSchemaContextProvider>(MockBehavior.Strict);
Mock<IAmbientProvider> ambientProviderMock = new Mock<IAmbientProvider>(MockBehavior.Strict);
Mock<XamlSchemaContext> xamlSchemaContextMock = new Mock<XamlSchemaContext>(MockBehavior.Strict);
xamlSchemaContextMock.Setup(xsc => xsc.GetXamlType(It.IsAny<Type>())).Returns((Func<Type, XamlType>)(t => GetXamlTypeForType(t, xamlSchemaContextMock.Object)));
ambientProviderMock.Setup(ap => ap.GetAllAmbientValues(null, false, It.IsAny<IEnumerable<XamlType>>(), It.IsAny<XamlMember[]>())).Returns((Func<IEnumerable<XamlType>, bool, IEnumerable<XamlType>, XamlMember[], IEnumerable<AmbientPropertyValue>>)GetAllAmbientValuesImplementation);
xamlSchemaContextProviderMock.Setup(xscp => xscp.SchemaContext).Returns(xamlSchemaContextMock.Object);
serviceProviderMock.Setup(s => s.GetService(typeof(IXamlSchemaContextProvider))).Returns(xamlSchemaContextProviderMock.Object);
serviceProviderMock.Setup(s => s.GetService(typeof(IAmbientProvider))).Returns(ambientProviderMock.Object);
serviceProviderMock.Setup(s => s.GetService(It.IsNotIn(typeof(IXamlSchemaContextProvider), typeof(IAmbientProvider)))).Returns(null);
return serviceProviderMock.Object;
IEnumerable<AmbientPropertyValue> GetAllAmbientValuesImplementation(IEnumerable<XamlType> ceilingTypes, bool searchLiveStackOnly, IEnumerable<XamlType> types, params XamlMember[] properties)
{
Mock<ResourceDictionary> resourceDictionaryMock = new Mock<ResourceDictionary>(MockBehavior.Strict);
resourceDictionaryMock.Protected().Setup("OnGettingValue", false, ItExpr.Is<object>(o => resources.Any(kvp => kvp.Key.Equals(o))), ItExpr.Ref<object>.IsAny, ItExpr.Ref<bool>.IsAny).CallBase();
foreach (KeyValuePair<object, object> kvp in resources)
{
resourceDictionaryMock.Object.Add(kvp.Key, kvp.Value);
}
Mock<AmbientPropertyValue> ambientPropertyValueMock = new Mock<AmbientPropertyValue>(MockBehavior.Strict, null, resourceDictionaryMock.Object);
return new List<AmbientPropertyValue> { ambientPropertyValueMock.Object };
}
XamlType GetXamlTypeForType(Type t, XamlSchemaContext xamlSchemaContext)
{
Mock<XamlType> xamlTypeMock = new Mock<XamlType>(MockBehavior.Strict, t, xamlSchemaContext);
xamlTypeMock.Protected().Setup<XamlMember>("LookupMember", true, "Resources", false).Returns((Func<string, bool, XamlMember>)LookupMemberImplementation);
xamlTypeMock.Protected().Setup<XamlMember>("LookupMember", true, "BasedOn", false).Returns((Func<string, bool, XamlMember>)LookupMemberImplementation);
xamlTypeMock.Setup(xt => xt.ToString()).CallBase();
return xamlTypeMock.Object;
XamlMember LookupMemberImplementation(string name, bool skipReadOnlyCheck)
{
Mock<PropertyInfo> propertyInfoMock = new Mock<PropertyInfo>(MockBehavior.Strict);
propertyInfoMock.Setup(pi => pi.Name).Returns(name);
propertyInfoMock.Setup(pi => pi.DeclaringType).Returns(t);
Mock<XamlMember> xamlMemberMock = new Mock<XamlMember>(MockBehavior.Strict, propertyInfoMock.Object, xamlSchemaContext);
xamlMemberMock.Setup(xm => xm.ToString()).CallBase();
return xamlMemberMock.Object;
}
}
}