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();
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();
If I understand the question correctly, I'll assume that we have classes like these:
Specifically, I've added the properties
Foo
,Bar
, andBaz
to each of those classes to emphasise that while one may be interested in settingx.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
:On
One
:and on
Two
:This enables you to use
Fixture
'sGet
extension method to produce anX
value with a particularFirst.Second.Third
value, but where all other values are populated freely by AutoFixture.The following test passes:
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 usingx
,first
, andsecond
.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
andsecond
values, sincex
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:
A lens is a pair of functions. The
Getter
returns the value of a property, given a 'full' object. TheSetter
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:
Set
andGet
simply enables you to get the value of a property, or to set a property to a particular value. The interesting function here isCompose
, which enables you to compose a lens fromT
toU
with a lens fromU
toV
.This works best if you have static lenses defined for each class, for example for
X
:One
:Two
: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:
You take
X.FirstLens
, which is a lens fromX
toOne
and first compose it withOne.SecondLens
, which is a lens fromOne
toTwo
. The result so far is a lens fromX
toTwo
.Since this is a Fluent Inteface, you can keep going and compose this lens with
Two.ThirdLens
, which is a lens fromTwo
tostring
. The final, composed lens is a lens fromX
tostring
.You can then use the
Set
extension method to set this lens onx
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
, wherefirst
,second
, andthird
are lenses.