hey Charles you got a second?
Sure man what's up?
Well, I've been writing these unit tests
Just like you showed me but —
I don't know, man
they're starting to feel kinda messy
Hmm
Let me take a look
So, looking at your test class
I see that it's called "InventoryTests"
and it looks like you're taking advantage
of nested classes, so we have the
"AddBag" and "RemoveBag" classes nested
So that leads me to believe you've got
the Inventory as the system under test
and then you're testing both the AddBag method
and then the RemoveBag method respectively
(Charles) is that right? (Elliot) Yup, that's right!
(Charles) Awesome. And I can also see that
you're using fluent assertions which
is great because your assertions are very clear
I see that after you've added a bag to this
inventory that has space for one bag that
the list of bags should include that bag
So that's great
and scrolling down I can tell that you're
basically having to instantiate an inventory with
multiple different states in order to test
this AddBag method under different conditions
And you know what?
I think I can see where the problem may lie
in order to create an inventory that
has space for multiple bags you
you need to instantiate multiple bags
and then pass them into the constructor in a
very particular way
now the constructor for bag is pretty
simple it seems to only take an integer
called capacity but even with just that
one parameter your test is already
looking a little bloated in other words
the problem here is that your creational logic
is too complex and it's sort of
muddying up the waters of your test
and I can imagine that if you were to add
even more parameters to the Bag constructor
that this will get even harder to follow
and not to mention harder to maintain, too
Exactly! So what do I do?
You know what?
I think I've got just the thing to solve your problem
It's called the " Builder Pattern"
The Builder Pattern? What's that?
The Builder pattern is a Design Pattern that
separates the construction of a complex object
from its representation
it's typically used to encapsulate the creation
of different variations of the same object
but in our case, we can use a subset
of the pattern to make all of your hard to follow
creation logic much more expressive and
easier to follow, too
That sounds great! Where do we begin?
let's start by creating a builder for the bag class
since it's so simple
after that we'll move on to the inventory class
so we can start refactoring these tests
once we have those in place I'll show you a cool
trick that'll make these tests way more expressive
and easier to read
(Charles) Sounds good?
(Elliot) Yeah sounds good to me
Awesome - let's start by adding a new class to
our test project
we're gonna call it "BagBuilder
now the most important method on our
builder class is the build method that returns an
instance of the object that our builder is
in charge of building
so we're just going to return for now a new bag
It looks like the Bag class's constructor takes
an integer called capacity - so the idea is
instead of passing this value in on the constructor
we'll make this more explicit by providing a method
called WithCapacity - just to encapsulate the
setting of that value
now what's key here is that we don't actually want to return void we want to
return the bag builder so we can chain multiple methods like this along if we
add more properties to bag that need to be set on the constructor like maybe max
capacity and min capacity and things like that we can add them as methods to
the bag builder and then string them along building the bag as we go with a
more expressive interface of course we aren't doing anything just yet with this
method so what we're gonna need to do is create a private field to hold the
capacity which will set in the width capacity method and then use when the
build method is called let's go ahead and switch back to the inventory test
and use our new builder so we'll just go ahead and pick the first one here I'm
just gonna replace this creation logic with a call to our bag builder so we'll
say new bag builder with capacity 1 dot build and that is gonna produce a bag it
doesn't look that much better to be honest I know don't worry this is only
one step we're just laying down the groundwork
ok ok I'm gonna trust you on this you'll see we're gonna add some really cool
syntactic sugar but first why don't we implement a builder for the inventory so
again I'm gonna create a new class and I'm gonna call this inventory builder
and just like before the key here is that we're going to add a new method
called build that returns an inventory
now this is a little trickier because the inventory actually has an overload
to its constructor it could either take just an integer that represents the
capacity of the inventory or it could take a capacity and a set of parameters
that represent an array of bags so knowing that why don't we go ahead and
add two new private member variables to the inventory builder class one will be
an inn to represent the capacity and the other will be an array of bags and then
we can go ahead and pass those into the new inventory now of course we want to
be able to expose these on the Builder so
two new methods both of which will turn the inventory builder and one will be
again with capacity which will set the private member variable and return this
and then another one which will allow us to set the bags so we'll call this with
bags and when we add this into the inventory test you'll see how we can
actually chain these together and how nice that looks now we're almost done
there's one little caveat here there is a chance that with bags won't actually
get called so our bags array will be null in this case we're just gonna do a
null check and we're going to instantiate a new array of bags with the
capacity in that scenario because we don't want to have to force the user to
call with bags on the Builder every time all right let's switch back to the
inventory tests and now we can replace this call to the inventory constructor
with a call to a new inventory builder and again we'll say with capacity and
we'll also say with bags now pass in the bag and then call build all right I can
see the look on your face and don't worry now we're gonna add the syntactic
sugar starting by encapsulating these two builders into some classes that will
read a little bit better so first thing we're gonna do is create a new class
file called builders and in this class file we're gonna add two static classes
one called a and another one called run and this will be made clear in a moment
so we're gonna have a bag and an inventory so I'm going to add a public
static reference to a new bag builder in the class called bag it's gonna return a
new bag builder and then I'm gonna do the same thing for inventory in the
unclassified an inventory now you might be wondering
why I'm calling an inventory builder an inventory and that will be made clear in
a moment but first things first we can go back to our inventory tests and now
we no longer have to call this instantiation logic we can just call a
dot bag and same with the inventory builder we can call uninvent or e all
right so that's looking a little bit better but I really want to encapsulate
the idea of a builder I don't even want you to really know that there's a
builder being associated with this test all I want you to be concerned with as
someone who's reading this tests in the future is that I've got a bag with a
capacity of one and I've got an inventory with a capacity of one with
this set of bags so the next thing we're gonna do is get rid of this this call to
dot build and a really cool way we can do that is with an implicit cast so
let's go on over to the bag builder and we're gonna add a new method it's gonna
be a public static implicit operator that returns a bag and so this method
takes a bag builder call this builder and what we can do is just simply return
builder dot build ok so with this implicit operator in place we can go
back to our inventory tests and remove the call to dot build now what's
happening here is that this variable holds a bag builder but when you pass
the bag builder into a method that expects a list of bags it's implicitly
casting them to bags now to sort of get ahead of this we can change this from a
var and explicitly call this a bag and that call to dot build in the implicit
cast operator will happen right here and to be honest I kind of like this a
little bit better cuz when you're reading tests you want everything to be
very very explicit all right so let's go on over to the inventory builder and
we'll do the same thing again we're gonna add a public static implicit
operator that returns an inventory takes in an inventory builder and again it's
gonna return a call to builder dot build ok now we can go back to the inventory
tests and go ahead and remove the call to dot
build and then instead of using VAR we're going to explicitly call this what
it is an inventory and that should look a little bit better and if we fast
forward a little bit and scroll through this code we can see that it's much
cleaner and hopefully anyone who comes along in the future and has to debug one
of these tests when they break will have a very clear idea of what's going on and
there you have it Wow this is great it's so much more
expressive and way easier to follow and it's easier to maintain too now if you
make a change to your constructor or the class itself
you only need to update one place in your test suite thanks for the help and
thank you for watching this video if you enjoyed it or found it helpful the best
thing you can do to help support this channel is to share it with a friend and
of course like leave a comment and subscribe for more content just like
this last but not least a special shout out to my top patrons Wayne glows trond
Thomas and Z dark-wash photography and breck