You might have been at the same point where i have been a few times: You want to start with integration tests on an existing Magento 2 store. You follow the steps to setup your IDE, start the first test and wait. After a while you get one of these nasty non descriptive errors:
Base table or view not found: 1146 Table 'database-name-test.store_website' doesn't exist Base table or view not found: 1146 Table 'database-name-test.core_config_data' doesn't exist
But why is this happening? And how should we fix this?
Mage Dispatch is a newsletter for the community and by the community. Here you can share links that you think that the community should know about. We will include it in our next newsletter.
Why is this happening?
To fully understand the problem and its solution we need to begin at the start. When you run the integration testsuite Magento actually runs the bin/magento setup:install
command for you on the empty database. This means that your code must be installable on an empty database.
In theory this should be the case for every store, but in practice this never happens. All code is developed and executed in the context of an existing database. So when the integration tests are run, the setup is run from a different context.
The errors above are caused by the fact that Magento tries to access the store_website
and core_config_data
tables before they even are created. So the error, makes sense, but why is this happening?
For some reason, during the setup:install
process the list of CLI commands is called. One of these commands has a dependency on the StoreManagerInterface, which is a pretty common dependency. This tries to fetch database from the database from its constructor, which is bad. We can avoid calling its constructor by using proxies.
Deep dive into the Object manager
This error is caused when Magento tries to initialize the \Magento\Framework\Console\CommandListInterface
. When we register a new command we basically add our command to the constructor of its implementation:
<type name="Magento\Framework\Console\CommandList"> <arguments> <argument name="commands" xsi:type="array"> <item name="my_command_name" xsi:type="object">VENDOR\MODULE\Command\IMPLEMENTATION</item> </argument> </arguments> </type>
When the object manager is called to initialize a class, every dependency is resolved. This means that when you have 100 commands, Magento tries to initialize 100 classes. So when we find the loop where this happens we can find the error.
This loop is located in:
\Magento\Framework\ObjectManager\Factory\AbstractFactory::parseArray
The line where this is failing is the one that contains:
$array[$key] = $this->objectManager->get($item['instance']);
There are 2 ways to find the failing class:
Put an xdebug breakpoint on the line and keep pressing F8 until the error happens. Big downside of this is that you need to press this dozens of times and at the moment this happens you can't see the current command anymore.
Put a temporary try/catch block around it. This is easier, but you have to edit the code in de vendor folder for this.
For option 2: Replace the line by this code:
try { $array[$key] = $this->objectManager->get($item['instance']); } catch (\Exception $exception) { throw new \Exception('Failing command: ' . $key . ' instance: ' . $item['instance']); }
You need to throw an exception as an echo get's eaten by the parent command. Also please note: Remove this code when you're done!. This is for development only.
Tip: You need to delete the contents of the dev/tests/integration/tmp
folder between runs, otherwise the error will change and to no help.
Fixing the bug
When you have found the command that is causing this error we can finally fix this error. Fixing the bug can be challeging too, as it's not always obvious which dependency is causing this issue. You might have dependencies which have dependencies which have dependencies which are relying on the StoreManager, so i may be nested. Basically there are 2 ways to fix this kind of bugs:
Add the
Magento_Store
module to your modulessequence
inetc/module.xml
.Replace all arguments with proxies. This will speed up your
bin/magento
calls a little bit too.
There are 2 ways to switch the classes for proxies:
By appending
\Proxy
to the classname in the constructor, which is good for debugging.By appending
\Proxy
to the classname indi.xml
, which is the recommended way, plus, you are able to do this for other third party modules you can't alter directly. This looks likes this:
<type name="<VENDOR>\<MODULE>\Console\Command\SuperImportantImport"> <arguments> <argument name="importService" xsi:type="object">\<VENDOR>\<MODULE>\Service\Import\SuperImportantService</argument> </arguments> </type>
The sequence part is easier. From:
<module name="<VENDOR>_<MODULE>" setup_version="1.0.0" />
To:
<module name="<VENDOR>_<MODULE>" setup_version="1.0.0"> <sequence> <module name="Magento_Store"/> </sequence> </module>
Please note that you might need to repeat this process a few times as there might be multiple classes that fail. So:
Run test, fail.
Fix broken class.
Delete
dev/tests/integration/tmp/*
.Repeat.
Common issues
I've encountered some issues while running this code:
I needed to delete the
dev/tests/integration/tmp/*
folder.You can't run the command using Magerun. You need to use
bin/magento setup:upgrade
.In some cases you may get the error
Invalid entity_type specified: catalog_product
. Check which module gives this error, and addMagento_Catalog
to the sequence of that module.
Want to respond?