Now that this example is executing really well...
let's go back and run all of the examples in this spec class.
Ah, oh yea!
it_is_initializable() is still failing:
too few arguments to the constructor: zero passed, expected one.
That makes sense: we added $this->beConstructedWith()
to our it_builds_enclosures_with_dinosaurs() example, but not to this example.
The easiest solution is just to...
ya know...
duplicate it!
But... it does kind of make me think:
it would be nice if we could call a method before each example was executed -
a method that could, sort of, set some things up.
After all, we're going to need
to call $this->beConstructedWith() in every single example.
Unfortunately...
this is not possible...
and the tutorial is now over.
Kidding! This is totally possible: by creating a function called let().
Yep! let() will be called once before each example function is executed.
So, let() then it_is_initializable(), then let() again and it_builds_enclosures_with_dinosaurs().
Inside let(), we can do the exact same things as our normal methods...
meaning, if we need a test double, we can add a DinosaurFactory $dinosaurFactory argument.
And this allows us to move the beConstructedWith() call up to let().
Ok, let's look at this: let() will be called first,
and will be passed the $dinosaurFactory test double.
But then, down in the example, we need to make sure that we get
that exact same DinosaurFactory test double object
because that is the object we need to add behavior to.
And that is exactly how this will work.
But, it's not just because these are both DinosaurFactory objects.
Nope. phpspec matches by the argument name: because the argument is called $dinosaurFactory
in let() and because it has the same name below, phpspec knows to pass the same object...
instead of creating a brand-new test double.
So... unless we've mucked something up, this should make everything happy!
Try it: Woohoo!
Let's do one last thing before you all go off and take
over the world with your new phpspec knowledge.
One of the things I want my EnclosureBuilderService to do is
to save the new EnclosureBuilder to the database after it finishes building it.
Now, obviously, we don't have a database in this application...
but who cares?
We can fake it!
Open up the tutorial/ directory: you'll have it if you downloaded the course code.
In the Service/ directory there is an EntityManagerInterface.php file.
Copy that and put it into your Service/ directory.
If you're a Doctrine user, this will look familiar.
But, no, I'm not recommending that you actually create
or move the EntityManagerInterface into your app like this.
We're adding this interface as a convenient way to "pretend" like Doctrine exists in our app.
This interface looks just like the one from Doctrine,
with the same persist() and flush() methods.
Back in the example, I want to describe that persist() and flush() should be called
on the EntityManagerInterface when we call buildEnclosure(): I want to guarantee
that I didn't forget to save this to the database.
And that means that our EnclosureBuilderService will have a second dependency.
In let(), add a second argument: EntityManagerInterface $entityManager.
Pass that as the second argument to beConstructedWith().
Then, down in the actual example,
we want to assert that persist() and flush() were called on it.
Get that same test double here by also saying EntityManagerInterface $entityManager.
At this point, we can use the mock or spy functionality...
it makes no difference.
I'll do it as a spy.
Start with $entityManager->persist().
This should be passed an Enclosure object.
So let's say Argument::type() with Enclosure::class.
Then, ->shouldHaveBeenCalled().
Repeat this with flush(), except that flush() doesn't take any arguments.
This time, we're not giving the test double any behavior: it's a pure spy.
Try it: Woo!
It fails: no calls have been made that match persist()
with type Enclosure, but expected at least one.
Open EnclosureBuilderService and let's code that up.
Start by adding the EntityManagerInterface $entityManager argument.
I'll hit Alt + Enter to create that property and set it.
Finally, at the bottom of the method, $this->entityManager->persist($enclosure)
and $this->entityManager->flush().
This looks great.
But let's run phpspec one last time to be sure: It passes!
Try the entire test suite: Hey!
We didn't break anything!
Friends! That's it.
phpspec is wonderful tool to help you write unit tests...
but really focus on the design of your class.
Yes, you do need to get used to a lot of the magic it does.
But once you embrace the magic, the experience is wonderful...
and the code generation isn't too bad either.
One word of warning that I always like to give people is this:
just because you have a wonderful testing tool like phpspec,
it doesn't mean you need to test every single thing.
For example, in DinosaurSpec, we're doing a lot of testing on the getter and setter methods.
You can do that...
but I think it's overkill.
Think of testing less as an "all-or-nothing" sort of thing, and more of a priority system:
make it a high priority to test, or describe the classes
that have a lot of complexity or that scare you.
Ok, get out there, describe some great classes, and we'll seeya next time!