How to build nested property with autofixture
Asked Answered
T

1

12

How to set nested property with autofixture (it's readonly)? Something like this:

var result =
    fixture.Build<X>()
    .With(x => x.First.Second.Third, "value")
    .Create();
Thunderclap answered 19/6, 2015 at 13:7 Comment(3)
If the property is read-only, wouldn't that mean you wouldn't (shouldn't) be able to set it anyway?Acting
I can do it as it is set in the constructor.Thunderclap
If it's read-only, you can't assign values to it, but here's how to deal with constructor arguments: https://mcmap.net/q/585197/-easy-way-to-specify-the-value-of-a-single-constructor-parameter/126014Trumpeter
T
16

If I understand the question correctly, I'll assume that we have classes like these:

public class X
{
    public X(One first, string foo)
    {
        First = first;
        Foo = foo;
    }

    public One First { get; }

    public string Foo { get; }
}

public class One
{
    public One(Two second, int bar)
    {
        Second = second;
        Bar = bar;
    }

    public Two Second { get; }

    public int Bar { get; }
}

public class Two
{
    public Two(string third, bool baz)
    {
        Third = third;
        Baz = baz;
    }

    public string Third { get; }

    public bool Baz { get; }
}

Specifically, I've added the properties Foo, Bar, and Baz to each of those classes to emphasise that while one may be interested in setting x.First.Second.Third to a specific value, one would still be interested in having all other properties populated by AutoFixture.

As a general observation, once you start working with immutable values, this is where a language like C# starts to reveal its limitations. While possible, it goes against the grain of the language.

There's plenty of other advantages to writing code with immutable data, but it gets tedious in C#. That's one of the reasons I finally gave up on C# and moved on to F# and Haskell. While this is a bit of a digression, I mention this to explicitly communicate that I think that using read-only properties is a fine design decision, but that it comes with some known problems.

In general, when working with immutable values, particularly in testing, it's a good idea to add copy-and-update methods to each immutable class, starting with X:

public X WithFirst(One newFirst)
{
    return new X(newFirst, this.Foo);
}

On One:

public One WithSecond(Two newSecond)
{
    return new One(newSecond, this.Bar);
}

and on Two:

public Two WithThird(string newThird)
{
    return new Two(newThird, this.Baz);
}

This enables you to use Fixture's Get extension method to produce an X value with a particular First.Second.Third value, but where all other values are populated freely by AutoFixture.

The following test passes:

[Fact]
public void BuildWithThird()
{
    var fixture = new Fixture();

    var actual =
        fixture.Get((X x, One first, Two second) =>
            x.WithFirst(first.WithSecond(second.WithThird("ploeh"))));

    Assert.Equal("ploeh", actual.First.Second.Third);
    Assert.NotNull(actual.Foo);
    Assert.NotEqual(default(int), actual.First.Bar);
    Assert.NotEqual(default(bool), actual.First.Second.Baz);
}

This uses an overload to Fixture.Get that takes a delegate with three input values. All those values are populated by AutoFixture, and you can then nest the copy-and-update methods using x, first, and second.

The assertions show that not only does actual.First.Second.Third have the expected value, but all other properties are populated as well.

Lenses

You may think that it seems redundant that you have to ask AutoFixture for the first and second values, since x should already contain those. Instead, you may want to be able to just 'reach into' First.Second.Third without having to deal with all of those intermediary values.

This is possible using lenses.

A lens is a construct with the origin in category theory, and used in some programming languages (most notably Haskell). Functional programming is all about immutable values, but even with functional languages with first-class support for immutable data, deeply nested immutable records are awkward when you just need to update a single datum.

I don't intend to turn this answer into a lenses tutorial, so if you really want to understand what's going on, search for a lenses tutorial in your favourite functional programming language.

In short, though, you can define a lens in C# like this:

public class Lens<T, V>
{
    public Lens(Func<T, V> getter, Func<V, T, T> setter)
    {
        Getter = getter;
        Setter = setter;
    }

    internal Func<T, V> Getter { get; }

    internal Func<V, T, T> Setter { get; }
}

A lens is a pair of functions. The Getter returns the value of a property, given a 'full' object. The Setter is a function that takes a value, and an old object, and returns a new object with the property changed to the value.

You can define a set of functions that operate on lenses:

public static class Lens
{
    public static V Get<T, V>(this Lens<T, V> lens, T item)
    {
        return lens.Getter(item);
    }

    public static T Set<T, V>(this Lens<T, V> lens, T item, V value)
    {
        return lens.Setter(value, item);
    }

    public static Lens<T, V> Compose<T, U, V>(
        this Lens<T, U> lens1,
        Lens<U, V> lens2)
    {
        return new Lens<T, V>(
            x => lens2.Get(lens1.Get(x)),
            (v, x) => lens1.Set(x, lens2.Set(lens1.Get(x), v)));
    }
}

Set and Get simply enables you to get the value of a property, or to set a property to a particular value. The interesting function here is Compose, which enables you to compose a lens from T to U with a lens from U to V.

This works best if you have static lenses defined for each class, for example for X:

public static Lens<X, One> FirstLens =
    new Lens<X, One>(x => x.First, (f, x) => x.WithFirst(f));

One:

public static Lens<One, Two> SecondLens =
    new Lens<One, Two>(o => o.Second, (s, o) => o.WithSecond(s));

Two:

public static Lens<Two, string> ThirdLens =
    new Lens<Two, string>(t => t.Third, (s, t) => t.WithThird(s));

This is boilerplate code, but it's straightforward once you get the hang of it. Even in Haskell it's boilerplate, but it can be automated with Template Haskell.

This enables you to write the test using a composed lens:

[Fact]
public void BuildWithLenses()
{
    var fixture = new Fixture();

    var actual = fixture.Get((X x) =>
        X.FirstLens.Compose(One.SecondLens).Compose(Two.ThirdLens).Set(x, "ploeh"));

    Assert.Equal("ploeh", actual.First.Second.Third);
    Assert.NotNull(actual.Foo);
    Assert.NotEqual(default(int), actual.First.Bar);
    Assert.NotEqual(default(bool), actual.First.Second.Baz);
}

You take X.FirstLens, which is a lens from X to One and first compose it with One.SecondLens, which is a lens from One to Two. The result so far is a lens from X to Two.

Since this is a Fluent Inteface, you can keep going and compose this lens with Two.ThirdLens, which is a lens from Two to string. The final, composed lens is a lens from X to string.

You can then use the Set extension method to set this lens on x to "ploeh". The assertions are the same as above, and the test still passes.

The lens composition looks verbose, but that's mainly an artefact of C# limited support for custom operators. In Haskell, a similar composition would literally look like first.second.third, where first, second, and third are lenses.

Trumpeter answered 26/3, 2018 at 20:45 Comment(2)
I love lenses as much as the next guy but I find them underpowered in C#. You can't express the Lens-Traversal-Setter-Fold subtype hierarchy because you're not allowed multiple inheritance. Coincidentally I did some thinking a couple of weeks ago about how "default interface methods" might help here (I named your Compose _ because I wanted the syntax to be terse.) You can express the subtypes directly, and overload resolution chooses the right return type for l1._(l2). Not happy with ITraversal though.Dizzy
I thought about putting this in a library once C#8 is out - it would complement my generic programming lib nicely, providing a richer vocabulary to talk about "rewritable" types. However, as you note, you have to manually write boilerplate lenses for every property in your system, which IMO is way too tedious to make any such library actually usable in production. (I'm letting the question of how to fix that percolate...) Also without this client code gets lumped with twice as many type paramsDizzy

© 2022 - 2024 — McMap. All rights reserved.