Laravel Microservices: Feature testing

Greg Radzio
5 min readAug 7, 2020

Video Serie:

I have made a video serie for this very topic, so if you do not understand some concepts below, it is a good reference point that covers a lot more why instead of what:

happy path feature testing in laravel
Sad path feature testing in Laravel

Introduction

I have been working in many companies and the pattern that I see over and over is that once a service or product takes off in a startup, it then needs some testing automation made by developers, otherwise teams ship software full of bugs to production. What I see over and over is poor execution on tests written by developer. I am not talking about advanced topics like Test Driven Development, Behavioural Driven Development or Contract Driven Testing — these are really hard to master.

What I am talking about is fundamentals: Unit tests, Integration Tests and E2E Tests. This article is about to explain you the bare fundamentals and focus on Feature Tests in Laravel.

However first things first:

Pyramid of Testing

The pyramid of testing is always a starting point when I am teaching my teams about writing decent tests. Why? Because it says it all and the rules are simple:

  • Structure your code according to layers that can be tested in separation
  • Unit tests: deal with a single class or function, if you have dependency, you must mock it. They are the base of your testing and should be the most common tests that you write, because they are fast to execute and they give precise feedback
  • Service test: deal with a domain layer, here you want to see all business use cases being validated and see if your code integrates internally. You might have a 100% unit test coverage and passing, but your software will not work or it will not do what business wants you to do. They are slightly slower and more difficult to maintain, so write them less and more precise than your Unit tests
  • UI tests: deal with your application as black box. These tests know nothing about layers inside your code, it just knows interface that it can use to communicate with your service and the outcome that it should receive. Example: REST, GraphQL, cli, queue or many more. These test are slow and often require application setup before it can be executed. Also they do not give a precise feedback of where the things broke, so use them the least, but be very precise about what you are testing. What I found our works best is: cover all your happy paths (tests that have correct data in) and at least 1 sad path (tests that have incorrect data in).

In Laravel the UI tests are called Feature test, they hit the REST API that you expose writing your microservices and this article is focusing on writing them.

Why did we start with UI (Feature) tests?

Assuming you have no testing whatsoever in your application. Then I strongly recommend to start with these test.

But why?

Because even though they are slow and give less feedback, they are the best sanity check for your application. You will benefit greatly knowing that your application is works fine, or doesn’t work at all. Of course do not base all your strategy on these tests, start with a happy and sad path(s) and work your way down the pyramid in order to cover more edge cases.

What are we building?

In this and next series, we will be building Users microservice in PHP using Laravel. There will be a lot of cool things to learn so follow we into a journey of writing scalable and maintainable services in Laravel.

I strongly recommend my Youtube playlist: Better Laravel Way.

Happy Path testing

The first thing you should do is to write a test that has correct data and checks that your service is returning correct data

class CreateUserTest extends TestCase
{
use RefreshDatabase;

private $email = 'email@email.com';
private $requestHeaders = [
'Accept' => 'application/json'
];
/**
*
@test
*
*
@return void
*/
public function shouldCreateUser()
{
$userData = [
'password' => 'password'
];
$response = $this->put('/v1/users/' . $this->email, $userData, $this->requestHeaders);

$response->assertStatus(Response::HTTP_CREATED);
$response->assertJson([
'data' => [
'email' => $this->email
]
]);
}
}

This is one way of implementing creation of the user. I strongly recommend to watch a videos on top of this article to see how I actually created it using Test Driven Development, however I will be more than happy to shed some light here:

  • I used PUT instead of POST because it allows me to create users in idempotent way. I can now easily re-write the logic to use Queue or another Adapter and still maintain the same functionality.
  • The password I am putting does not really matter, feel free to put something that fits your needs better
  • The response is super basic, the modelling of responses is out of scope of this simple example

Sad Path

Ok once you have your happy path, it is time to check some of your negative cases. Here you should be checking for all sorts of 4xx errors (depending on the business case).

In this simple example I am using 422 which represents a validation error:

/**
*
@test
* @dataProvider invalidUserDataProvider
*/
public function shouldThrowValidationError($invalidUserData)
{
$response = $this->put('/v1/users/' . $this->email, $invalidUserData, $this->requestHeaders);

$response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY);
}

public function invalidUserDataProvider(): array
{
return array_map(function($index) {
return [
[
'password' => str_repeat('a', $index)
]
];
}, range(0, 5));
}

Here you can also see that I have made a data provider for some validation rule on password field, can you guess what is it?

Conclusion

I hope this simple example shows how to create feature tests in laravel and now it will become standard for you whenever making a new endpoint in your application. Trust me, it is so worth it!

In the next series I will talk about other types of testing and how to structure layers in your application, so you can change your code easily whenever business requirements change (and oh they change a lot!)

And as always:

Stay curious and hungry for more knowledge!

If you find it interesting and want to work with us, please head in to Cobiro or our Linked in.

--

--

Greg Radzio

CTO at Cobiro, promoter of TDD, SOLID, DDD and Design Patterns and Agile.