Disable Module in integration tests / how is the sandbox config.php written

Since this is still relevant in 2020: you can disable modules during the integration tests' install.

bin/magento setup:install accepts --disable-modules, which you can use in /dev/tests/integration/etc/install-config-mysql.php

In the example below I'm disabling all the MSI modules. Which is useful if you for some reason can't replace those modules with composer replace (as described here https://www.integer-net.com/make-magento-2-small-again/).

return [
    'db-host'           => 'db',
    'db-user'           => 'root',
    'db-password'       => 'magento',
    'db-name'           => 'magento_integration_tests',
    'db-prefix'         => '',
    'backend-frontname' => 'backend',
    'admin-user'        => \Magento\TestFramework\Bootstrap::ADMIN_NAME,
    'admin-password'    => \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD,
    'admin-email'       => \Magento\TestFramework\Bootstrap::ADMIN_EMAIL,
    'admin-firstname'   => \Magento\TestFramework\Bootstrap::ADMIN_FIRSTNAME,
    'admin-lastname'    => \Magento\TestFramework\Bootstrap::ADMIN_LASTNAME,
    'es-hosts'          => 'elasticsearch:9200',
    'disable-modules'   => join(
        ',',
        [
            'Magento_Inventory',
            'Magento_InventoryApi',
            'Magento_InventoryCatalogApi',
            'Magento_InventoryConfigurationApi',
            'Magento_InventorySales',
            'Magento_InventoryMultiDimensionalIndexerApi',
            'Magento_InventoryReservationsApi',
            'Magento_InventoryIndexer',
            'Magento_InventorySalesApi',
            'Magento_InventorySourceDeductionApi',
            'Magento_InventorySourceSelectionApi'
        ]
    ),
];

The key disable-modules accepts a comma-separated list, which I've entered as a joined array which is easier to read/edit.


As I stated in my comment: it seems that Magento 2 enabled all modules by default when running an integration test. Why this is I don't know. I've already filed an issue for this since I think it's unexpected behavior.

As a (hopefully temporary) workaround for this I came up with a solution where I created a wrapper around the bootstrap (call it a "bootwrap" if you may).

So in my phpunit.xml, I set my boostrap to:

bootstrap="./etc/bootstrap.php"

And etc/bootstrap.php looks like this:

// Bootstrap wrapper, used to disable certain modules for integration tests.
$ds = DIRECTORY_SEPARATOR;

// Default bootstrap, as provided by Magento:
include_once __DIR__ . $ds . implode($ds, ['..', 'framework', 'bootstrap.php']);

// Copy the original config.php, that has all the disabled modules:
$tmpDir = \Magento\TestFramework\Helper\Bootstrap::getInstance()->getAppTempDir();
$dir = __DIR__ . $ds . implode($ds, ['..', '..', '..', '..', 'app', 'etc']);
copy(
    $dir . $ds . 'config.php',
    $tmpDir . $ds . implode($ds, ['etc', 'config.php'])
);

Basically, the only thing it does, is copy your app/etc/config.php to your dev/tests/integration/tmp/xxxxxx/etc-folder; effectively disabling the proper modules.

Update:

It looks like the above fix no longer works in Magento 2.2.5.

I have "fixed" it for now with yet another dirty hack. Instead of having to copy/paste the original config.php before my tests, I had to dig deeper into the bootstrapping-context.

So I copied the original framework/bootstrap.php to my etc.php and edited as follows:

  • I changed the require_once __DIR__ ...-statements to require_once __DIR__ . '/../framework' so all required files got loaded properly.
  • I replaced the $application-instantiating as follows:

Original:

$application = new \Magento\TestFramework\Application(
    $shell,
    $installDir,
    $installConfigFile,
    $globalConfigFile,
    $settings->get('TESTS_GLOBAL_CONFIG_DIR'),
    $settings->get('TESTS_MAGENTO_MODE'),
    AutoloaderRegistry::getAutoloader(),
    true
);

My version:

// Manipulate existing application class to inject the projects' config.php:
$application = new class(
    $shell,
    $installDir,
    $installConfigFile,
    $globalConfigFile,
    $settings->get('TESTS_GLOBAL_CONFIG_DIR'),
    $settings->get('TESTS_MAGENTO_MODE'),
    AutoloaderRegistry::getAutoloader(),
    true
) extends \Magento\TestFramework\Application
{
    /**
     * @inheritDoc
     */
    public function install($cleanup)
    {
        $this->_ensureDirExists($this->installDir);
        $this->_ensureDirExists($this->_configDir);

        $file = $this->_globalConfigDir . '/config.php';
        $targetFile = $this->_configDir . str_replace($this->_globalConfigDir, '', $file);

        $this->_ensureDirExists(dirname($targetFile));
        if ($file !== $targetFile) {
            copy($file, $targetFile);
        }

        parent::install($cleanup);
    }

    /**
     * @inheritDoc
     */
    public function isInstalled()
    {
        // Always return false, otherwise DB credentials will be empty
        return false;
    }
};

I had to use an anonymous class because the new instantiation of the Application prevented me from using proper dependency injection. What it basically does, is it copies the config.php-file before the original install()-method is called. So when a native installation starts, it uses the config.php of my project to determine which modules should be installed.

Also, I had to override the isInstalled()-method to always return false, because otherwise the database function would have the wrong credentials (isInstalled() would always return true if a config.php is found).

So this way the integration tests on our CI server will run on an installation that reflects our production setup.