Reusing SimpleMigrationStep to create a test fixture for tests

Hi everyone,

I’m struggling a bit with writing a unit test for an app I am developing.
Concretely, I have several tables that I create in a child class of SimpleMigrationStep linked with foreign keys (like it is also indicated in the App Development Tutorial).

For a unit test I do not want to use mockups, but would like to create a SQLite DB as test fixture. Of course I could create all the tables also in my test setup (more or less manually) with PDO, but I do not want to repeat myself and risk to make errors/differences to how my tables are defined in the SimpleMigrationStep.

So I simply would like to reuse the SimpleMigrationStep’s changeSchema method in my test fixture.
However, the changeSchema method needs the parameter schemaClosure of type Closure implementing the ISchemaWrapper interface.

I checked the Nextcloud GitHub repository as well as the repositories of other apps. But I am not able to find how I can properly instantiate a SchemaWrapper, Connection or ConnectionFactory by injecting a PDO object that I create.

The following code does not work, but should illustrate what I want to achieve. The test fixture I will then reuse in the setup method of my Unit test.

<?php
namespace OCA\myApp\tests\Unit;

use PDO;
require_once __DIR__ . '/../../lib/Migration/Version000100Date20230121122000.php';
use OCA\myApp\Migration\Version000100Date20230121122000;
use OCA\DB\ConnectionFactory;

class TestFixture {


  # ---------------------------------------------------------------------------
  private function createSchema(): PDO {

    print('create new PDO object');
    $pdo = new PDO('sqlite::memory:');
    print('PDO object created');
    $migration = new Version000100Date20230121122000();
    print('CREATED new Migration');

    # we cannot give a pdo directly, but have to create a schemaClosure, how?
    $connection = new ConnectionFactory($pdo);
    $schemaClosure =  new SchemaWrapper($connection)
    return $migration->createSchema($schemaClosure, []);
     
  }
}

The following is a snippet of my SimpleMigrationStep:

class Version000100Date20230121122000 extends SimpleMigrationStep {

  /** 
   * @param IOutput $output
   * @param ISchemaWrapper $schemaISchemaWrapper
   * @param array $options
   * @return null|ISchemaWrapper
   */
  public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {

    $this->createSchema($schemaClosure, $options);
  }

  public function createSchema(Closure $schemaClosure, array $options) {
    $schema = $schemaClosure();
    ....
  }

Any help is appreciated!
Thanks in advance!

Sven

PS: Of course I also checked the Community if a similar topic already exists but couldn’t find anything matching.

Unit tests usually don’t require a database connection, you test a component isolated. Please read Unit testing - Wikipedia and Integration testing - Wikipedia

We use integration tests in mail:

In a nutshell: We set up a fresh Nextcloud instance, install the mail app and run the integration tests afterward.

I don’t think it’s possible to use the components without a working Nextcloud installation.

In addition to what @kesselb said, in the cookbook we have also tests against the database. There I called them migration tests.

Just a side question for @kesselb: How long are your tests running? I am seeing a significant performance issue for the migration tests that need almost a minute to complete for a dozen migrations.


Trying to answer the OT’s question a bit more: A Closure is just the name of a pointer to an anonymous function. You could create them on the fly if you implemented/mocked all the stuff in the nextcloud core. I doubt that this is the way to go here.

image

The integration tests are usually quite fast.

That could work. I don’t think you have to implement much stuff. The ConnectionFactory only requires a SystemConfig object. That should be doable to mock, but then you run a setup that is different from Nextcloud, and why would you do something like that for the tests? :wink:

1 Like

Thanks for the swift answers!

This is indeed a good point. Based on my description, what I want is more of an integration test. And yes, for an integration test it would make more sense to have a Nextcloud installation and not just parts of the class hierarchy.

Reflecting more about my initial question, I guess what I also wanted is to test that my DB schema is correct. This is probably a test on its own and not just a test fixture for other tests. For example, testing that all tables are there, no foreign key was forgotten etc. So basically testing my particular changeSchema method.
Is there a recommended way to do this or is this a bit out of scope of Nextcloud / PHP in this particular case?

Thanks also for the pointers to Nextcloud/mail, I will have a look at its extensive test suite (and maybe I will also find an answer to the question I just asked :slight_smile: ).

Like cookbook/tests/Migration at master · nextcloud/cookbook · GitHub?

I understand the idea of having an integration test to validate that all migrations are applied / the schema is in a defined state. I think if something is wrong with your migration, the integration test should fail when installing the app. But there are edge cases like [db]: Remove not supported column comments for SQLite by susnux · Pull Request #36803 · nextcloud/server · GitHub and https://github.com/nextcloud/forms/pull/1479#issuecomment-1439234202.