tiagomatosweb's avatar

Laravel test different scenarios

Hi all,

I am doing testings in Laravel 5.3+. Everything is going well, but there is a particular case that there must be a better/quicker way to create unit test.

  • Lets say I have Project model which has an attribute called status.
  • Status can be draft, in_progress and closed
  • A project belongs to a user who is the owner.
  • The owner can add other users to a project as a participant.
  • The owner only can add users while a project is in draft.

So, my ProjectTest file would have one single method for each of the scenario above? For instance:

owner_can_add_users_to_draft_project(){ ... }
owner_cant_add_users_to_inprogress_project(){ ... }
owner_cant_add_users_closed_project(){ ... }

etc...

Is that you guys usually do or is there any other better approach to avoid repetition?

cheers!

0 likes
4 replies
click's avatar
click
Best Answer
Level 35

I personally do it the same way you do right now. It takes some time to write but these kind of tests are still understandable next year. Earlier I always tried to write as less code as possible by making all kind of 'smart' methods but in the end it became all more complex especially when you later had to add a new test case.

You should make the test as simple as possible with a clear description.

But....

if you have a lot of repetitive tests you have an option to use data providers. A simple example would be:

/**
 * @test
 * @dataProvider emailProvider
 */
public function only_valid_email_addresses_should_be_allowed($email, $expected)
{
    $this->assertEquas($expected, is_valid_email($email));
}

public function emailProvider()
{
    // this data provider will call the test method 3 times with 2 parameters. You can optionally give the key a name.
    return [
         'optional name' => ['[email protected]', true], 
         'optional name 2' =>['johndoe.com', false],
         'optional name 3' =>['[email protected]', true],
    ];
}

And maybe you can imagine that you could also turn this in something like:

/**
 * @test
 * @dataProvider permissionProvider
 */
public function owner_add_user_permission_test($expected, $projectStatus)
{
    // setup the project here with the given $projectStatus and test if you can add a user and if it matches your expectation
}

public function permissionProvider()
{
    return [
        'add user to in progress project should be ok' => [true, 'in progress'], 
         'add user to draft project should be ok' =>[true, 'draft'],
         'add user to closed project fail' =>[false, 'closed'],
    ];
}

This way you could simplify test by writing less code. But it can get messy and unreadable when you do this a lot and especially if you have more than 2 parameters. But it all depends to the thing your are trying to test

See also the documentation about PHPUnit dataproviders https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers

2 likes
tiagomatosweb's avatar

Hey @m-rk, Thank you for replying me.

Yeah, things will get much and much more complex. My example was only a simple sample. I will have participants, collaborators, reviewers, etc, and all depends on also the user status. So it is quite a lot logic and permissions.

I liked you example but I also agree that it can be messy very soon. In addition, other developers will not understand on the first look.

I will keep writing separated methods for the time being.

By the way, do you know how I run a method only once before the test suit starts? Currently, my logic is running every time before each function because I am using the setUp methods. I think this wastes time and slow down considerably the testing process.

Cheers and thank you for the tips.

click's avatar

Yes see the static method setUpBeforeClass() https://phpunit.de/manual/current/en/fixtures.html#fixtures.sharing-fixture.examples.DatabaseTest.php

That method will be called before each test class starts. So you could use this to to setup models for example that you can reuse through the rest of of your tests in that class.

If you really want something to be ran only once per test you could run it in a 'bootstrap' file. See https://phpunit.de/manual/current/en/organizing-tests.html

Your default phpunit.xml file will now probably use the vendor/autoload.php as a bootstrap file. You could change this to your own bootstrap file and run some extra php code that you want to run before you start running a set of tests. Do not forget to include the vendor/autoload.php file in your own custom bootstrap otherwise you will see errors about classes that can't be found.

<?xml version="1.0" encoding="UTF-8"?>
<phpunit 
   ....
   bootstrap="vendor/autoload.php"
   .....
>
   <testsuites>
      //...
   </testsuites>
</phpunit>

1 like

Please or to participate in this conversation.