C# AutoFixture.AutoMoq为一个构造函数参数提供已知值
我刚刚开始在单元测试中使用AutoFixture.AutoMoq,我发现它对于创建不关心特定值的对象非常有用。毕竟,匿名对象创建就是它的全部内容 当我关心一个或多个构造器参数时,我正在努力解决这个问题。以下面的组件为例:C# AutoFixture.AutoMoq为一个构造函数参数提供已知值,c#,unit-testing,autofixture,automocking,C#,Unit Testing,Autofixture,Automocking,我刚刚开始在单元测试中使用AutoFixture.AutoMoq,我发现它对于创建不关心特定值的对象非常有用。毕竟,匿名对象创建就是它的全部内容 当我关心一个或多个构造器参数时,我正在努力解决这个问题。以下面的组件为例: public class ExampleComponent { public ExampleComponent(IService service, string someValue) { } } 我想编写一个测试,其中我为someValue提供了一个特
public class ExampleComponent
{
public ExampleComponent(IService service, string someValue)
{
}
}
我想编写一个测试,其中我为someValue
提供了一个特定的值,但将IService
留给AutoFixture.AutoMoq自动创建
我知道如何在我的iTexture
上使用Freeze
来保持将注入组件的已知值,但我不太明白如何提供自己的已知值
以下是我理想情况下想要做的:
[TestMethod]
public void Create_ExampleComponent_With_Known_SomeValue()
{
// create a fixture that supports automocking
IFixture fixture = new Fixture().Customize(new AutoMoqCustomization());
// supply a known value for someValue (this method doesn't exist)
string knownValue = fixture.Freeze<string>("My known value");
// create an ExampleComponent with my known value injected
// but without bothering about the IService parameter
ExampleComponent component = this.fixture.Create<ExampleComponent>();
// exercise component knowning it has my known value injected
...
}
我正在实现一个CookieCache
,其中ITypeConverter
处理对象与字符串之间的转换,并使用lifespan
设置cookie的到期日期
public class CookieCache : ICache
{
public CookieCache(ITypeConverter converter, TimeSpan lifespan)
{
// usual storing of parameters
}
public bool TryRead<T>(string key, out T result)
{
// read the cookie value as string and convert it to the target type
}
public void Write<T>(string key, T value)
{
// write the value to a cookie, converted to a string
// set the expiry date of the cookie using the lifespan
}
// other methods not shown...
}
公共类CookieCache:ICache
{
公共CookieCache(ITypeConverter转换器,时间跨度寿命)
{
//参数的常规存储
}
公共bool TryRead(字符串键,out T结果)
{
//以字符串形式读取cookie值并将其转换为目标类型
}
公共无效写入(字符串键,T值)
{
//将值写入cookie,并转换为字符串
//使用寿命设置cookie的到期日期
}
//其他方法未显示。。。
}
因此,在编写cookie有效期测试时,我关心的是寿命,而不是转换器。您必须更换:
string knownValue = fixture.Freeze<string>("My known value");
您可以阅读有关Inject
的更多信息
实际上,
Freeze
扩展方法可以:
var value = fixture.Create<T>();
fixture.Inject(value);
return value;
var value=fixture.Create();
夹具。注入(值);
返回值;
这意味着您在测试中使用的重载实际上使用seed:My known value调用了Create
,结果是“My known value4d41f94f-1fc9-4115-9f29-e50bc2b4ba5e” 你可以这样做。假设您想为名为lifespan
的TimeSpan
参数指定一个特定值
public class LifespanArg : ISpecimenBuilder
{
private readonly TimeSpan lifespan;
public LifespanArg(TimeSpan lifespan)
{
this.lifespan = lifespan;
}
public object Create(object request, ISpecimenContext context)
{
var pi = request as ParameterInfo;
if (pi == null)
return new NoSpecimen(request);
if (pi.ParameterType != typeof(TimeSpan) ||
pi.Name != "lifespan")
return new NoSpecimen(request);
return this.lifespan;
}
}
必要的是,它可以这样使用:
var fixture = new Fixture();
fixture.Customizations.Add(new LifespanArg(mySpecialLifespanValue));
var sut = fixture.Create<CookieCache>();
var fixture=newfixture();
fixture.Customizations.Add(newlifespanarg(mySpecialLifespanValue));
var sut=fixture.Create();
这种方法在某种程度上可以推广,但最终,我们受到了限制,因为缺乏从特定构造函数或方法参数中提取参数信息的强类型方法。因此,我相信人们可以解决Mark建议的推广实现,但我想我会发表评论 我根据Mark的
LifeSpanArg
,创建了一个通用参数NameSpecimenBuilder
:
public class ParameterNameSpecimenBuilder<T> : ISpecimenBuilder
{
private readonly string name;
private readonly T value;
public ParameterNameSpecimenBuilder(string name, T value)
{
// we don't want a null name but we might want a null value
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentNullException("name");
}
this.name = name;
this.value = value;
}
public object Create(object request, ISpecimenContext context)
{
var pi = request as ParameterInfo;
if (pi == null)
{
return new NoSpecimen(request);
}
if (pi.ParameterType != typeof(T) ||
!string.Equals(
pi.Name,
this.name,
StringComparison.CurrentCultureIgnoreCase))
{
return new NoSpecimen(request);
}
return this.value;
}
}
以下测试现在将通过:
[TestMethod]
public void FreezeByName_Sets_Value1_And_Value2_Independently()
{
//// Arrange
IFixture arrangeFixture = new Fixture();
string myValue1 = arrangeFixture.Create<string>();
string myValue2 = arrangeFixture.Create<string>();
IFixture sutFixture = new Fixture();
sutFixture.FreezeByName("value1", myValue1);
sutFixture.FreezeByName("value2", myValue2);
//// Act
TestClass<string> result = sutFixture.Create<TestClass<string>>();
//// Assert
Assert.AreEqual(myValue1, result.Value1);
Assert.AreEqual(myValue2, result.Value2);
}
public class TestClass<T>
{
public TestClass(T value1, T value2)
{
this.Value1 = value1;
this.Value2 = value2;
}
public T Value1 { get; private set; }
public T Value2 { get; private set; }
}
[TestMethod]
公共名称\u独立设置\u值1\u和\u值2\u()
{
////安排
iTexture arrangeFixture=新夹具();
字符串myValue1=arrangeFixture.Create();
字符串myValue2=arrangeFixture.Create();
iTexture sutFixture=新夹具();
sutFixture.FriezByName(“value1”,myValue1);
sutFixture.FriezByName(“value2”,myValue2);
////表演
TestClass结果=sutFixture.Create();
////断言
arest.AreEqual(myValue1,result.Value1);
Assert.AreEqual(myValue2,result.Value2);
}
公共类TestClass
{
公共测试类(T值1,T值2)
{
此值为1.Value1=Value1;
此参数为0.Value2=Value2;
}
公共T值1{get;私有集;}
公共T值2{get;私有集;}
}
我觉得@Nick就快到了。重写构造函数参数时,它需要用于给定类型,并且仅限于该类型
首先,我们创建一个新的ISpecimenBuilder,它查看“Member.DeclaringType”以保持正确的范围
public class ConstructorArgumentRelay<TTarget,TValueType> : ISpecimenBuilder
{
private readonly string _paramName;
private readonly TValueType _value;
public ConstructorArgumentRelay(string ParamName, TValueType value)
{
_paramName = ParamName;
_value = value;
}
public object Create(object request, ISpecimenContext context)
{
if (context == null)
throw new ArgumentNullException("context");
ParameterInfo parameter = request as ParameterInfo;
if (parameter == null)
return (object)new NoSpecimen(request);
if (parameter.Member.DeclaringType != typeof(TTarget) ||
parameter.Member.MemberType != MemberTypes.Constructor ||
parameter.ParameterType != typeof(TValueType) ||
parameter.Name != _paramName)
return (object)new NoSpecimen(request);
return _value;
}
}
public类构造函数argumentrelay:ISpecimenBuilder
{
私有只读字符串_paramName;
私有只读TValueType_值;
公共构造函数ArgumentRelay(字符串参数名、TValueType值)
{
_paramName=paramName;
_价值=价值;
}
公共对象创建(对象请求,ISPecementContext上下文)
{
if(上下文==null)
抛出新的ArgumentNullException(“上下文”);
ParameterInfo参数=请求作为ParameterInfo;
if(参数==null)
返回(对象)新的NoSpecimen(请求);
if(parameter.Member.DeclaringType!=typeof(TTarget)||
parameter.Member.MemberType!=MemberTypes.Constructor||
parameter.ParameterType!=typeof(TValueType)||
parameter.Name!=\u paramName)
返回(对象)新的NoSpecimen(请求);
返回_值;
}
}
接下来,我们创建一个扩展方法,使我们能够轻松地将其与AutoFixture连接起来
public static class AutoFixtureExtensions
{
public static IFixture ConstructorArgumentFor<TTargetType, TValueType>(
this IFixture fixture,
string paramName,
TValueType value)
{
fixture.Customizations.Add(
new ConstructorArgumentRelay<TTargetType, TValueType>(paramName, value)
);
return fixture;
}
}
公共静态类AutoFixtureExtensions
{
公共静态iTexture构造函数参数(
这个IFixture夹具,
字符串参数名,
TValueType(类型值)
{
fixture.Customizations.Add(
新建构造函数ArgumentRelay(参数名、值)
);
返回夹具;
}
}
现在我们创建两个类似的类来进行测试
public class TestClass<T>
{
public TestClass(T value1, T value2)
{
Value1 = value1;
Value2 = value2;
}
public T Value1 { get; private set; }
public T Value2 { get; private set; }
}
public class SimilarClass<T>
{
public SimilarClass(T value1, T value2)
{
Value1 = value1;
Value2 = value2;
}
public T Value1 { get; private set; }
public T Value2 { get; private set; }
}
公共类TestClass
{
公共测试类(T值1,T值2)
{
Value1=Value1;
Value2=Value2;
}
公共T值1{get;私有集;}
公共T值2{get;私有集;}
}
公共类相似类
{
公共相似类(T值1,T值2)
{
Value1=Value1;
Value2=Value2;
}
公共T值1{get;私有集;}
公共T值2{get;私有集;}
}
最后我们测试
public class ConstructorArgumentRelay<TTarget,TValueType> : ISpecimenBuilder
{
private readonly string _paramName;
private readonly TValueType _value;
public ConstructorArgumentRelay(string ParamName, TValueType value)
{
_paramName = ParamName;
_value = value;
}
public object Create(object request, ISpecimenContext context)
{
if (context == null)
throw new ArgumentNullException("context");
ParameterInfo parameter = request as ParameterInfo;
if (parameter == null)
return (object)new NoSpecimen(request);
if (parameter.Member.DeclaringType != typeof(TTarget) ||
parameter.Member.MemberType != MemberTypes.Constructor ||
parameter.ParameterType != typeof(TValueType) ||
parameter.Name != _paramName)
return (object)new NoSpecimen(request);
return _value;
}
}
public static class AutoFixtureExtensions
{
public static IFixture ConstructorArgumentFor<TTargetType, TValueType>(
this IFixture fixture,
string paramName,
TValueType value)
{
fixture.Customizations.Add(
new ConstructorArgumentRelay<TTargetType, TValueType>(paramName, value)
);
return fixture;
}
}
public class TestClass<T>
{
public TestClass(T value1, T value2)
{
Value1 = value1;
Value2 = value2;
}
public T Value1 { get; private set; }
public T Value2 { get; private set; }
}
public class SimilarClass<T>
{
public SimilarClass(T value1, T value2)
{
Value1 = value1;
Value2 = value2;
}
public T Value1 { get; private set; }
public T Value2 { get; private set; }
}
[TestFixture]
public class AutoFixtureTests
{
[Test]
public void Can_Create_Class_With_Specific_Parameter_Value()
{
string wanted = "This is the first string";
string wanted2 = "This is the second string";
Fixture fixture = new Fixture();
fixture.ConstructorArgumentFor<TestClass<string>, string>("value1", wanted)
.ConstructorArgumentFor<TestClass<string>, string>("value2", wanted2);
TestClass<string> t = fixture.Create<TestClass<string>>();
SimilarClass<string> s = fixture.Create<SimilarClass<string>>();
Assert.AreEqual(wanted,t.Value1);
Assert.AreEqual(wanted2,t.Value2);
Assert.AreNotEqual(wanted,s.Value1);
Assert.AreNotEqual(wanted2,s.Value2);
}
}
internal sealed class CustomConstructorBuilder<T> : ISpecimenBuilder
{
private readonly Dictionary<string, object> _ctorParameters = new Dictionary<string, object>();
public object Create(object request, ISpecimenContext context)
{
var type = typeof (T);
var sr = request as SeededRequest;
if (sr == null || !sr.Request.Equals(type))
{
return new NoSpecimen(request);
}
var ctor = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public).FirstOrDefault();
if (ctor == null)
{
return new NoSpecimen(request);
}
var values = new List<object>();
foreach (var parameter in ctor.GetParameters())
{
if (_ctorParameters.ContainsKey(parameter.Name))
{
values.Add(_ctorParameters[parameter.Name]);
}
else
{
values.Add(context.Resolve(parameter.ParameterType));
}
}
return ctor.Invoke(BindingFlags.CreateInstance, null, values.ToArray(), CultureInfo.InvariantCulture);
}
public void Addparameter(string paramName, object val)
{
_ctorParameters.Add(paramName, val);
}
}
public static class AutoFixtureExtensions
{
public static void FreezeActivator<T>(this IFixture fixture, object parameters)
{
var builder = new CustomConstructorBuilder<T>();
foreach (var prop in parameters.GetType().GetProperties())
{
builder.Addparameter(prop.Name, prop.GetValue(parameters));
}
fixture.Customize<T>(x => builder);
}
}
var f = new Fixture();
f.FreezeActivator<UserInfo>(new { privateId = 15, parentId = (long?)33 });
var sut = new Fixture()
.For<AClass>()
.Set("value1").To(aInterface)
.Set("value2").ToEnumerableOf(22, 33)
.Create();
public class AClass
{
public AInterface Value1 { get; private set; }
public IEnumerable<int> Value2 { get; private set; }
public AClass(AInterface value1, IEnumerable<int> value2)
{
Value1 = value1;
Value2 = value2;
}
}
public interface AInterface
{
}
public class ATest
{
[Theory, AutoNSubstituteData]
public void ATestMethod(AInterface aInterface)
{
var sut = new Fixture()
.For<AClass>()
.Set("value1").To(aInterface)
.Set("value2").ToEnumerableOf(22, 33)
.Create();
Assert.True(ReferenceEquals(aInterface, sut.Value1));
Assert.Equal(2, sut.Value2.Count());
Assert.Equal(22, sut.Value2.ElementAt(0));
Assert.Equal(33, sut.Value2.ElementAt(1));
}
}
public static class AutoFixtureExtensions
{
public static SetCreateProvider<TTypeToConstruct> For<TTypeToConstruct>(this IFixture fixture)
{
return new SetCreateProvider<TTypeToConstruct>(fixture);
}
}
public class SetCreateProvider<TTypeToConstruct>
{
private readonly IFixture _fixture;
public SetCreateProvider(IFixture fixture)
{
_fixture = fixture;
}
public SetProvider<TTypeToConstruct> Set(string parameterName)
{
return new SetProvider<TTypeToConstruct>(this, parameterName);
}
public TTypeToConstruct Create()
{
var instance = _fixture.Create<TTypeToConstruct>();
return instance;
}
internal void AddConstructorParameter<TTypeOfParam>(ConstructorParameterRelay<TTypeToConstruct, TTypeOfParam> constructorParameter)
{
_fixture.Customizations.Add(constructorParameter);
}
}
public class SetProvider<TTypeToConstruct>
{
private readonly string _parameterName;
private readonly SetCreateProvider<TTypeToConstruct> _father;
public SetProvider(SetCreateProvider<TTypeToConstruct> father, string parameterName)
{
_parameterName = parameterName;
_father = father;
}
public SetCreateProvider<TTypeToConstruct> To<TTypeOfParam>(TTypeOfParam parameterValue)
{
var constructorParameter = new ConstructorParameterRelay<TTypeToConstruct, TTypeOfParam>(_parameterName, parameterValue);
_father.AddConstructorParameter(constructorParameter);
return _father;
}
public SetCreateProvider<TTypeToConstruct> ToEnumerableOf<TTypeOfParam>(params TTypeOfParam[] parametersValues)
{
IEnumerable<TTypeOfParam> actualParamValue = parametersValues;
var constructorParameter = new ConstructorParameterRelay<TTypeToConstruct, IEnumerable<TTypeOfParam>>(_parameterName, actualParamValue);
_father.AddConstructorParameter(constructorParameter);
return _father;
}
}
public class ConstructorParameterRelay<TTypeToConstruct, TValueType> : ISpecimenBuilder
{
private readonly string _paramName;
private readonly TValueType _paramValue;
public ConstructorParameterRelay(string paramName, TValueType paramValue)
{
_paramName = paramName;
_paramValue = paramValue;
}
public object Create(object request, ISpecimenContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
ParameterInfo parameter = request as ParameterInfo;
if (parameter == null)
return new NoSpecimen();
if (parameter.Member.DeclaringType != typeof(TTypeToConstruct) ||
parameter.Member.MemberType != MemberTypes.Constructor ||
parameter.ParameterType != typeof(TValueType) ||
parameter.Name != _paramName)
return new NoSpecimen();
return _paramValue;
}
}