Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test a Laravel package that handles Eloquent models?

Tags:

I've created a package that manipulates eloquent models, and I've created test cases for it.

The package is supposed to call save() or delete() for the models, at some point, which would attempt to hit the database.

Actual Database approach

As first attempt I tried this approach and also this. The point is that in the context of a package (even using orchestra/testbench) I'd need to configure a database and migrations. Since the package itself does not have any model (but I've created a dummy model for test purposes) I see this approach as possibly overkill. In any case, I'd still accept to setup a prepared sqlite db in memory, which I also tried but could not make it work (It was attempting to use forge connection instead sqlite and I could not make it access another connection. I may provide details on what I did for it).

Mocking approach

Another possible attempt (as per my limited understanding on this) is to partially mock the model. But after I mocked it, it would not know how to handle other calls such as fill(), for which I was willing the common behavior, but I received a method not found exception.

Method override approach

Given that these two possible attempts went bad, I defaulted to a third possible approach which worked for me, but I'm honestly not sure if this is the way to go about it.

In order to avoid test failure due to lack of database when calling save() or delete() methods, I overrided them (full source code here) :

class DummyContact extends Model
{
    // ...

    public function save(array $options = [])
    {
        $this->exists = true;
        $this->wasRecentlyCreated = true;
    }

    public function delete()
    {
        $this->exists = false;
        $this->wasRecentlyCreated = false;
    }
}

This way, I'm able to test the following code (full source here):

public function unifyOnBase()
{
    $mergeModel = $this->merge();

    $this->modelA->fill($mergeModel->toArray());
    $this->modelA->save();
    $this->modelB->delete();

    return $this->modelA;
}

So my question is, is this approach acceptable? (I think it is fair, I don't see exceptional pitfalls, but I suspect there exist more elegant approaches). In case there are suggested approaches, like Mocking or using an actual DB for running the tests, I'd like to know what adaptations should I do to my testing codebase for taking them.

Final but important note: I'm not willing to test models itself, I'm willing to test my code that uses (and thus depends on models).

like image 536
alariva Avatar asked Sep 13 '18 02:09

alariva


2 Answers

Thanks to comment of Jonas Staudenmeir:

An integration test with an actual database is the most thorough way to test your code. IMO, you should at least have integration tests for the fundamental features. This is how I use an SQLite database for the tests of my package.

I could pair and get working the in-memory sqlite approach.

  1. Removed override functions

  2. Added Capsule test case setUp code as per this working project as example (Thanks for that)

  3. In my case I also required to install the sqlite driver sudo apt-get install php7.2-sqlite

Tests now still run successfully, while the solution looks more elegant and cleans-up the function override workarounds which would easily break upon API upgrades of Eloquent Models. This would also enable easier access to testing relationship-dependent features of the package.

Test pass

like image 199
alariva Avatar answered Oct 11 '22 16:10

alariva


I think your first proposal, the actual database approach is the best - using the package orchestra/testbench.

Since you have no Eloquent models in your package, but your package modifies Eloquent models, I think you should create an Eloquent model only for testing purposes inside your test folder.

For example, put the DummyContact into tests/Models/DummyContact.php and put the migration file into tests/Database/Migration/DummyContactMigration.php.

Now all you need to do, is to setup a basic TestCase.php. Make sure to explicitly call the migration file of your DummyContact model.

Here is an example:

<?php

namespace MyVendor\MyPackage\Tests;

use MyVendor\MyPackage\MyServiceProvider;

class TestCase extends \Orchestra\Testbench\TestCase
{
    public function setUp(): void
    {
        parent::setUp();

        $this->loadMigrationsFrom(__DIR__ . '/database/migrations');
        $this->artisan('migrate', ['--database' => 'testbench'])->run();

    }

    /**
     * add the package provider
     *
     * @param $app
     * @return array
     */
    protected function getPackageProviders($app)
    {
        return [MyServiceProvider::class];
    }

    /**
     * Define environment setup.
     *
     * @param  \Illuminate\Foundation\Application  $app
     * @return void
     */
    protected function getEnvironmentSetUp($app)
    {
        // Setup default database to use sqlite :memory:
        $app['config']->set('database.default', 'testbench');
        $app['config']->set('database.connections.testbench', [
            'driver'   => 'sqlite',
            'database' => ':memory:',
            'prefix'   => '',
        ]);
    }
}

You may also want to read this blog post

like image 31
Adam Avatar answered Oct 11 '22 17:10

Adam