AutoFixture.AutoMoq supply a known value for one constructor parameter
Asked Answered
U

6

31

I've just started to use AutoFixture.AutoMoq in my unit tests and I'm finding it very helpful for creating objects where I don't care about the specific value. After all, anonymous object creation is what it is all about.

What I'm struggling with is when I care about one or more of the constructor parameters. Take ExampleComponent below:

public class ExampleComponent
{
    public ExampleComponent(IService service, string someValue)
    {
    }
}

I want to write a test where I supply a specific value for someValue but leave IService to be created automatically by AutoFixture.AutoMoq.

I know how to use Freeze on my IFixture to keep hold of a known value that will be injected into a component but I can't quite see how to supply a known value of my own.

Here is what I would ideally like to do:

[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
    ...
}

I know I could do this by calling the constructor directly but this would no longer be anonymous object creation. Is there a way to use AutoFixture.AutoMock like this or do I need to incorporate a DI container into my tests to be able to do what I want?


EDIT:

I probably should have been less absract in my original question so here is my specific scenario.

I have an ICache interface which has generic TryRead<T> and Write<T> methods:

public interface ICache
{
    bool TryRead<T>(string key, out T value);

    void Write<T>(string key, T value);

    // other methods not shown...  
}

I'm implementing a CookieCache where ITypeConverter handles converting objects to and from strings and lifespan is used to set the expiry date of a 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...
}

So when writing a test for the expiry date of a cookie, I care about the lifespan but not so much about the converter.

Ungracious answered 29/5, 2013 at 16:37 Comment(5)
Why do you want to do this? What is the scenario? IME, scenarios like this tend to smell like mixed concerns in ExampleComponent. There's a reason AutoFixture doesn't support this out of the box.Adeline
@MarkSeemann what do you think about my scenario in the edited question? I don't think this can be interpreted as mixed concerns.Ungracious
Well, it's pretty difficult for me to tell, because I don't see how you intend to use lifespan. Doesn't lifespan have to interact with the current time? Once you start to think about questions such as those, perhaps an abstraction still emerges. The last time I did something like this, I arrived at an ILease interface instead, which made the cache logic much more flexible because I could now support: Absolute Expiry, Sliding Window Expiry, LRU Expiry, and lots of other options.Adeline
@MarkSeemann I like the sound of an ILease and I think I have to stand corrected when I said my solution couldn't be interpreted as mixed concerns. Since editing the question I have added an IDateTimeProvider dependency to my CookieCache and I set the expiry date of a cookie by adding the lifespan to the current date. I realise now that this really is a mixed concern, even though that mixed concern only took one line of code!Ungracious
possible duplicate of How do I use Autofixture (v3) with ICustomization, ISpecimenBuilder to deal with constructor parameter?Spalla
A
21

You have to replace:

string knownValue = fixture.Freeze<string>("My known value");

with:

fixture.Inject("My known value");

You can read more about Inject here.


Actually the Freeze extension method does:

var value = fixture.Create<T>();
fixture.Inject(value);
return value;

Which means that the overload you used in the test actually called Create<T> with a seed: My known value resulting in "My known value4d41f94f-1fc9-4115-9f29-e50bc2b4ba5e".

Astrology answered 29/5, 2013 at 17:31 Comment(3)
FWIW, while this answer is a good explanation of how Freeze and Inject works, the proposed solution would cause all strings to get the value "My known value", which may not be what the OP wants.Adeline
@MarkSeemann - is there some way that I can use AutoFixture to set up different known values of the same type in a constructor?Ungracious
@MarkSeemann surely there are legitimate occasions when a class has two different string dependencies (or two dependencies of any type) and they need to be set to different values for a unit test? For example I have a requirement to produce a shopping basket style solution (not an actual shopping basket) which has two different capacities: maximum X items of one type and maximum Y items of another type. I need to set these up to be different values in unit tests so rather than using AutoFixture for these tests I've used a builder class.Ungracious
U
22

So I'm sure people could work out the generalized implementation of Mark's suggestion but I thought I'd post it for comments.

I've created a generic ParameterNameSpecimenBuilder based on Mark's LifeSpanArg:

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;
    }
}

I've then defined a generic FreezeByName extension method on IFixture which sets the customization:

public static class FreezeByNameExtension
{
    public static void FreezeByName<T>(this IFixture fixture, string name, T value)
    {
        fixture.Customizations.Add(new ParameterNameSpecimenBuilder<T>(name, value));
    }
}

The following test will now pass:

[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; }
}
Ungracious answered 6/6, 2013 at 8:28 Comment(3)
Worked like a charm. Made a little hybrid overload, where the frozen value is still auto-generated: public static T FreezeByName<T>(this IFixture fixture, string name) { var value = fixture.Create<T>(); fixture.Customizations.Add(new ParameterNameSpecimenBuilder<T>(name, value)); return value; }Angleaangler
This is cool, thanks Nick. I think the only issue that I can pick up is when you use the same fixture instance to create another class instance which also has a "name" parameter in the constructor of type string. Chances of that happening is slim but, just saying. Would you agree?Hypostyle
Yes I agree that is a limitation but I agree it's also not very likely to cause you any real issues.Ungracious
A
21

You have to replace:

string knownValue = fixture.Freeze<string>("My known value");

with:

fixture.Inject("My known value");

You can read more about Inject here.


Actually the Freeze extension method does:

var value = fixture.Create<T>();
fixture.Inject(value);
return value;

Which means that the overload you used in the test actually called Create<T> with a seed: My known value resulting in "My known value4d41f94f-1fc9-4115-9f29-e50bc2b4ba5e".

Astrology answered 29/5, 2013 at 17:31 Comment(3)
FWIW, while this answer is a good explanation of how Freeze and Inject works, the proposed solution would cause all strings to get the value "My known value", which may not be what the OP wants.Adeline
@MarkSeemann - is there some way that I can use AutoFixture to set up different known values of the same type in a constructor?Ungracious
@MarkSeemann surely there are legitimate occasions when a class has two different string dependencies (or two dependencies of any type) and they need to be set to different values for a unit test? For example I have a requirement to produce a shopping basket style solution (not an actual shopping basket) which has two different capacities: maximum X items of one type and maximum Y items of another type. I need to set these up to be different values in unit tests so rather than using AutoFixture for these tests I've used a builder class.Ungracious
A
15

You could do something like this. Imagine that you want to assign a particular value to the TimeSpan argument called lifespan.

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;
    }
}

Imperatively, it could be used like this:

var fixture = new Fixture();
fixture.Customizations.Add(new LifespanArg(mySpecialLifespanValue));

var sut = fixture.Create<CookieCache>();

This approach can be generalized to some degree, but in the end, we're limited by the lack of a strongly typed way to extract a ParameterInfo from a particular constructor or method argument.

Adeline answered 6/6, 2013 at 6:1 Comment(1)
thanks for this. I think I'll add a generic extension method based on this for use in future tests.Ungracious
B
11

I fee like @Nick was almost there. When overriding the constructor argument, it needs to be for the given type and have it limited to that type only.

First we create a new ISpecimenBuilder that looks at the "Member.DeclaringType" to keep the correct scope.

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;
    }
}

Next we create an extension method to allow us to easily wire it up with 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;
    }
}

Now we create two similar classes to test with.

    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; }
    }

Finally we test it with an extension of the original test to see that it will not override similarly named and typed constructor arguments.

[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);
    }        
}
Burgener answered 5/1, 2015 at 6:41 Comment(0)
K
8

This seems to be the most comprehensive solution set here. So I'm going to add mine:

The first thing to create ISpecimenBuilder that can handle multiple constructor parameters

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);
    }
 }

Then create extension method that simplifies usage of created builder

   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);
        }
    }

And usage:

var f = new Fixture();
f.FreezeActivator<UserInfo>(new { privateId = 15, parentId = (long?)33 });
Kopans answered 20/2, 2015 at 11:11 Comment(1)
Nice and clean solution. On our projects sometimes we have multiple dependencies for the same type and we only want to test one of then, this named parameter fits very well for our needs. Thanks for sharing.Smalls
B
1

Good thread, I added another twist based on many of the aswers already posted:

Usage

Example:

var sut = new Fixture()
    .For<AClass>()
    .Set("value1").To(aInterface)
    .Set("value2").ToEnumerableOf(22, 33)
    .Create();

Test classes:

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
{
}

Full test

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));
    }
}

Infrastructure

Extension method:

public static class AutoFixtureExtensions
{
    public static SetCreateProvider<TTypeToConstruct> For<TTypeToConstruct>(this IFixture fixture)
    {
        return new SetCreateProvider<TTypeToConstruct>(fixture);
    }
}

Classes participating in the fluent style:

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;
    }
}

Constructor parameter relay from other answers:

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;
    }
}
Barrios answered 30/9, 2019 at 15:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.