Start using given->when->then approach to boost up your PHP tests

Wojciech Pilich
Docplanner Tech
Published in
6 min readJan 20, 2021
Software testing, unit tests

Do you ever feel confused and overwhelmed when trying to write reader-friendly and self-explanatory unit tests in PHP? The answer is most likely yes. But no worries, I experienced the same many times. Fortunately, there is an easy way to handle that. How? Just change the way you write your tests. It’s as simple as that. Let me show you how it works in my every-day programmer work.

First things first, I won’t focus on specific tests implementation because it depends on your coding environment such as domain objects, class methods etc. I also assume that you have some experience in writing tests and using, for example, PHPUnit. Moreover, I plan to write a separate article about the best techniques for writing tests in PHP.

For now, let’s stick to general ideas and focus on enhancing the flow and clarity of your tests. There’s nothing worse than messed-up and illegible tests.

First thing - realize what should be tested, not how

Let’s assume that you just finished implementing a module responsible for handling user profiles in your application. It checks which type of profile the user has and decides about the score used by another module in a different place of your application. Sounds familiar, doesn’t it?

Your team leader asked you to cover this functionality with unit tests fully. That’s all you know about the task, and you didn’t get any other clues or tips. Okay, so you start writing tests… stop! Why?

Have a deep breath and think about what are you going to test: a class or even a single method. Then divide those things into separate unit tests. The most important goal is to avoid mixing up many code responsibilities in a single test. That’s why you should withstand the pressure of instant coding and wrap your head around it. Believe me, this kind of preparation is priceless.

One responsibility = one test and nothing more

To check user profiles, you probably need to have a so-called resolver class. Let’s call it UserProfileResolver. It can look like this:

final class UserProfileResolver implements Resolvable
{
public method resolve(ProfileInterface $profile): ProfileScore
{
// resolves user profile...
}
(...)
}

This class is responsible for handling user profiles and nothing more. It performs some checks and calculations in resolve() method which returns a ProfileScore class. It’s a simple value object that stores the computed score for a given user profile. That’s all in a big picture.

To check if resolving the profile process works fine, you should test the resolve() method mentioned above. Other operations that occur inside this method are not important from this test point of view. So don’t bother with them. They should be tested separately.

Given block— here you prepare test data

You have a test class called UserProfileResolverTest.php which can look like this:

final class UserProfileResolverTest extends TestCase
{
public function testItResolvesUserProfile(): void
{
// your tests here...
}
}

The first thing is to prepare all classes (with their dependencies)necessary during the resolving process. This is a moment when a given private method comes into play!

private function givenUserProfile(): void
{
$this->userProfile = $this->factory::createWith(self::TEST_ID);
}

As you see, this short method does the job. It calls the factory class and creates an an instance of use profile with some test id value. It perfectly sticks to the DRY principle because every time you want to use the user profile, you just call the $this->userProfile property. That’s all. No duplicating assignments etc.

So now we have our first given block which will be used inside the test method. This method is responsible for preparing data for our test. And that’s what matters here. This is how it looks like:

final class UserProfileResolverTest extends TestCase
{
public function testItResolvesUserProfile(): void
{
$this->givenUserProfile();
}
}

It’s so clean and easy to understand while reading, huh? That’s the point :) So far so good but we are still missing when block. Let’s add it now.

When block —here you execute different actions

You probably know what’s next! We have to execute the resolve() method on the user profile. To do that we have to create separate when private method. It will be responsible for resolving user profile:

private function whenResolves(): void
{
$this->score = $this->resolver->resolve($this->userProfile);
}

Here we used the recently created user profile and passed it as a parameter to resolver’s resolve() method. Again, so simple! Now our test method looks like that:

final class UserProfileResolverTest extends TestCase
{
public function testItResolvesUserProfile(): void
{
$this->givenUserProfile();
$this->whenResolves();
}
}

Then block — here you put assertions and checks

Okay, we already have given and when blocks. But so far we lack assertions and other checks which means that our tests are not completed yet. Now it’s time for creating then method. To be 100% sure that everything went well, let’s make three assertions.

private function thenProfileGetsScore(): void
{
$expectedScore = new ProfileScore(self::TEST_SCORE);
self::assertEquals($expectedScore, $this->score);
self:assertNotEmpty($this->score);
self::assertInstanceOf(ProfileScore::class, $this->score);
}

As you see, we created a local variable called $expectedScore which is compared with the final output from the resolve() method. This is the most important check that our test does. If this part won’t pass, it can mean two things:

  • we expected wrong score value,
  • resolve() method failed to compute the score.

Once our test passed and glowed green, we are good to go and can create another minor but quite important assertions like:

  • assertNotEmpty — to exclude empty value,
  • assertInstanceOf — to be sure we got proper value object with score.

You can put more assertions if you wish, but in this case, those three are more than enough.

More complex test? — no problem — just stick to the rules

In real life, you will often need to have more given, when and then blocks. So how do you handle them when having more complex checks to perform?

It’s simple. Just add more methods that start with andGiven, andWhen or andThen and then combine them into blocks in the the main test method. Remember about the right order of execution to avoid unexpected errors. A more complicated example can look like this:

final class MyTest extends TestCase
{
public function testItWorksFine(): void
{
$this->givenSomeTestData();
$this->andGivenAnotherImportantTestData();
$this->andGivenOtherNecessaryTestData();
$this->whenDoingSomething();
$this->andWhenDoingAnotherThing();
$this->thenSomethingIsHappening();
$this->andThenAnotherThingHappens();
}
}

That’s all :) our test is now clear, well-organized and fully-readable. Let’s make a final recap and write down the essential rules:

  • before writing tests spare a moment to plan what you will test and isolate the code responsibilities which should be tested,
  • divide each test into three blocks: given (preparing), when (executing) and then (asserting),
  • use those private methods in the right order in every test method — like in the example,
  • keep all necessary test data in test class fields (like $this->userProfile, $this->resolver etc.) — you will be able to use them in the whole test easily,
  • in a perfect scenario, your test methods will contain only execution of private block methods — given, when and then.

All in all, as you see, writing tests in PHP can be easy and fun. It’s just a matter of preparation and choosing the right approach. I showed you a recipe for nice-looking tests that are almost as readable as a book.

I encourage you to try those rules in your own tests. Fingers crossed, and I hope you enjoyed my article :) don’t hesitate to contact me if you have any doubts or questions!

Do you enjoy the article? Follow our profile and visit our other channels: Docplanner.tech website, Facebook, Twitter, and LinkedIn.

--

--