Easy way to specify the value of a single constructor parameter?
Asked Answered
D

1

18

Is there some easy way in Autofixture to new-up an object using it's constructor, but hard-code/specify the value to use for a single parameter in the constructor?

I see that this can be done with properties, using something like:

fixture.Build<MyObj>().With(x=x.Val,"hard coded value").Create()

Dormie answered 5/2, 2015 at 17:13 Comment(5)
C# doesn't really have a type-safe syntax for referring to only a single parameter in a method (including a constructor), so this isn't really doable unless you pull in the entire constructor into a lambda expression, at which point you might as well just create the object directly using the constructor, but without AutoFixture. What are your motivation for wanting to do this? IME, there's often a good way to achieve most related goals in a different way, but you may have to look at the problem differently.Vale
Was hoping for some non type-safe, but convenient way like a Func<string,T> where the input would be the name of the parameter as a string. My general motivation is unit testing. Often I want to new-up an object, but I'm only concerned about a single parameter. Maybe the parameter value drives a calculation or transform of some sort. I want the value itself to be random, but the test needs to know what random value was chosen. Given that, maybe the unit test gets a Property or calls a method that uses the constructor parameter value in it's operation.Dormie
some variation of this answer or my answer toward the bottom of this CodePlex discussion might make you happyAvuncular
that's exactly it! @MarkSeemann, any way to get something like this built-into AutoFixture? It seems like an incredibly useful, yet basic paradigm (choosing constructor values) that's not support out-of-the-box.Dormie
@SFun28 I've edited my answer to add another section that explains how to do something similar using the provided building blocks; I'm sure you can figure out how to create an API around that if you want to hide some of the plumbing.Vale
V
18

From the discussion about this question in the comments:

Often I want to new-up an object, but I'm only concerned about a single parameter. Maybe the parameter value drives a calculation or transform of some sort. I want the value itself to be random, but the test needs to know what random value was chosen.

Assuming that this is the underlying problem, I'll attempt to address this issue.

Ask the object

The easiest solution is to simply ask the object what the value is, after AutoFixture created it:

var fixture = new Fixture();
var mc = fixture.Create<MyClass>();
var specialValue = mc.SpecialValue;
// Do something with specialValue...

If you just need to know what the special value is, but you don't need it to have a particular value, you can use this technique.

This obviously requires you to expose the value from the constructor parameter as a (read-only) property, but that's a good idea to do anyway.

Assign the value after creation

If you need the parameter to have a specific value, you can assign it after creation:

var fixture = new Fixture();
var mc = fixture.Create<MyClass>();
mc.SpecialValue = "Some really special value";

This requires you to make the value available as a writeable property. While I'm personally not a big fan of that, because that makes the object mutable, many people already design their objects that way, and if that's the case, why not take advantage of it?

Use copy-and-update after creation

If you want your objects to be immutable, you can still use a variation of the above technique. In F#, you can use so-called copy and update expressions to achieve the same goal. In F#, it'd be something like:

let fixture = Fixture ()
let mc = {
    fixture.Create<MyRecord> ()
    with SpecialValue = "Some special value!" }

You can emulate this in C# by giving your classes copy-and-update methods, so you'd be able to write something like this:

var fixture = new Fixture();
var mc = fixture
    .Create<MyClass>()
    .WithSpecialValue("Some really special value");

This is a technique I use all the time in my C# code. AutoFixture has an idiomatic assertion to test such copy and update methods.

Inject a value for a parameter

If you can live with injecting a fixed value for a particular constructor parameter, you can do this with the building blocks of the AutoFixture kernel. This test demonstrates how:

[Fact]
public void CustomizeParameter()
{
    var fixture = new Fixture();
    fixture.Customizations.Add(
        new FilteringSpecimenBuilder(
            new FixedBuilder("Ploeh"),
            new ParameterSpecification(
                typeof(string),
                "specialValue")));

    var actual = fixture.Create<MyClass>();

    Assert.Equal("Ploeh", actual.SpecialValue);
}

However, this causes that parameter to always be "Ploeh" for that fixture instance. It's also refactoring-unsafe, since it's base on a string referring to the name of the parameter.

Vale answered 6/2, 2015 at 9:49 Comment(6)
hi Mark - many thanks for the detailed reply. These are all good workarounds, with the tradeoffs exactly as you've presented them. The common theme is that you have to tweak your object so that it can be tested. That's a bit of a code smell for me. It doesn't seem like there's a way to accomplish what I'm looking to do without modifying the object.Dormie
It's called Test-Driven Development. The whole point is that tests provide feedback about the usability of the API design.Vale
Only if you are using TDD =) I think Autofixture is incredibly useful independent of TDD. And anyways, isn't it still a smell in TDD? The only reason you're adding those properties is because of a deficiency in Autofixture. It has nothing to do with the principles of TDD.Dormie
That's great; one of the marks of good software is when people use it in other ways that it was originally designed for, so that's cool, but it was very explicitly designed as a TDD tool.Vale
it's actually a lot safer now than it used to be since the introduction of the nameof operator new ParameterSpecification( typeof(string), nameof(MyClass.SpecialValue)));Felafel
If you want your objects to be immutable, I've found that using init only properties to be an acceptable workaround/solution... public string FirstName { get; init; } You are still able to use AutoFixture's With method to set the property value.Breakdown

© 2022 - 2024 — McMap. All rights reserved.