2
\$\begingroup\$

I have created a test, also the interfaces to be implemented for each test case based on user's role because I think it would make it easier to understand what the test case will and should do, also forces each test that has similar requirements to have consistent method naming. I want to make sure what I did is a best practice before I implement them to all my test cases.

To have a better sight of what am I doing, please have a look at this folder structure.

visual explanation, folder structure

Based on that, I have implemented them in one class, the file looks like this. It applies to other test cases like Showing, Updating, Destroying, Uploading, and so on...

<?php

namespace Tests\Feature\Specialization;

use App\Models\Specialization;
use App\Models\User;
use Database\Seeders\SpecializationSeeder;
use Database\Seeders\RolesAndPermissionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Spatie\Permission\PermissionRegistrar;
use Tests\_Interfaces\Feature\Basic\Indexable\IndexableByCustomer;
use Tests\_Interfaces\Feature\Basic\Indexable\IndexableByEmployee;
use Tests\_Interfaces\Feature\Basic\Indexable\IndexableByInternal;
use Tests\_Interfaces\Feature\Basic\Indexable\IndexableByModerator;
use Tests\_Interfaces\Feature\Basic\NotIndexable\NotIndexableByGuest;
use Tests\TestCase;

class SpecializationIndexTest extends TestCase implements
    IndexableByCustomer,
    IndexableByEmployee,
    IndexableByInternal,
    IndexableByModerator,
    NotIndexableByGuest
{
    use RefreshDatabase;

    public function setUp(): void
    {
        parent::setUp();
        $this->app->make(PermissionRegistrar::class)->registerPermissions();
        $this->seed(RolesAndPermissionSeeder::class);
        $this->seed(SpecializationSeeder::class);
    }

    public function testIndexShouldBeInaccessibleByGuest()
    {
        $this
            ->getJson(route('specializations.index'))
            ->assertUnauthorized()
            ->assertJsonMissing(Specialization::first()->toArray());
    }

    public function testIndexShouldBeAccessibleByInternal()
    {
        $user = User::factory()->createOne()->assignRole('internal');

        $this->actingAs($user, 'api')
            ->getJson(route('specializations.index'))
            ->assertOk()
            ->assertJsonFragment(Specialization::first()->toArray());
    }

    public function testIndexShouldBeAccessibleByModerator()
    {
        $user = User::factory()->createOne()->assignRole('moderator');

        $this->actingAs($user, 'api')
            ->getJson(route('specializations.index'))
            ->assertOk()
            ->assertJsonFragment(Specialization::first()->toArray());
    }

    public function testIndexShouldBeAccessibleByCustomer()
    {
        $user = User::factory()->createOne()->assignRole('customer');

        $this->actingAs($user, 'api')
            ->getJson(route('specializations.index'))
            ->assertOk()
            ->assertJsonFragment(Specialization::first()->toArray());
    }

    public function testIndexShouldBeAccessibleByEmployee()
    {
        $user = User::factory()->createOne()->assignRole('employee');

        $this->actingAs($user, 'api')
            ->getJson(route('specializations.index'))
            ->assertOk()
            ->assertJsonFragment(Specialization::first()->toArray());
    }
}

So the question is, is this a good idea to have interfaces for tests? Or should I avoid it?

\$\endgroup\$
5
  • 1
    \$\begingroup\$ Other then forcing some method names over many tests, I don't see the point. Maybe if implementation of those methods is the same across many tests then you may implement traits instead, but the interface seems rather useless, is it typehinted anywhere? \$\endgroup\$ Commented Nov 30, 2020 at 4:44
  • 1
    \$\begingroup\$ For an interface to be useful, someone must call it's methods not knowing the actual class. Here the tests runner calls public methods starting with test* or marked as a test explicitly with an annotation. It doesn't really care whether those interfaces are implemented, it just cares that the class is a test case child. \$\endgroup\$ Commented Nov 30, 2020 at 4:49
  • \$\begingroup\$ Yes yes.. I ended up deleting them because the file started to bloat everytime I add new requirements to the test. Well, I do have to remember them (the method name, and the requirements) tho since now didn't have autocompletion, to keep my test tidy and make sure not skipping any. \$\endgroup\$ Commented Nov 30, 2020 at 12:54
  • \$\begingroup\$ I tend to use interfaces when I have a base class to make sure that child classes have correct methods. But, unlike what you are doing, those methods are support methods and if nothing is set then methods from the parent class are used. \$\endgroup\$ Commented Dec 25, 2020 at 9:44
  • \$\begingroup\$ Could you please share what these interfaces include? \$\endgroup\$ Commented Oct 24, 2021 at 8:45

1 Answer 1

2
\$\begingroup\$

I'm not sure what you're using these interfaces for, but I can see all the tests have the same code and the only thing is changing is the role. Why not using phpunit data provider instead?

The code will be like this:

// for the guest case, you can leave it as it is.

/**
     * @dataProvider roles
    */
    public function testIndexShouldBeAccessibleBySupportedRoles($role)
    {
        $user = User::factory()->createOne()->assignRole($role);
   

        $this->actingAs($user, 'api')
            ->getJson(route('specializations.index'))
            ->assertOk()
            ->assertJsonFragment(Specialization::first()->toArray());
    }
    public function roles()
    {
        return [
            ['internal'],
            ['customer'],
            ['employee'],
            ['moderator'],
        ];
    }

When you want to test a new role, all you have to do is adding the new role to the roles provider.

So my answer to your question is No The tests shouldn't care about your app implementations details, by using these interfaces you're coupling your tests to your app. And for every new role you have to create:

  • A test for the new role.
  • a new interface. and if you decide to change your interface name, you must change all tests that are implementing this interface.
\$\endgroup\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.