Is it okay to use interfaces on tests?

Posted on

Problem

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 TestsFeatureSpecialization;

use AppModelsSpecialization;
use AppModelsUser;
use DatabaseSeedersSpecializationSeeder;
use DatabaseSeedersRolesAndPermissionSeeder;
use IlluminateFoundationTestingRefreshDatabase;
use IlluminateFoundationTestingWithFaker;
use SpatiePermissionPermissionRegistrar;
use Tests_InterfacesFeatureBasicIndexableIndexableByCustomer;
use Tests_InterfacesFeatureBasicIndexableIndexableByEmployee;
use Tests_InterfacesFeatureBasicIndexableIndexableByInternal;
use Tests_InterfacesFeatureBasicIndexableIndexableByModerator;
use Tests_InterfacesFeatureBasicNotIndexableNotIndexableByGuest;
use TestsTestCase;

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?

Solution

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.

Leave a Reply

Your email address will not be published. Required fields are marked *