After this simple example, let's write another test that illustrates a more complex scenario. And we will write one that tests the CsvImporter plugin we created in the previous chapter.
There is quite a lot of functionality that goes into this plugin and working with it--we have the actual importing, the plugin and configuration entity creation, the user interface for doing so and so on. And it's a very good example of functionality that can benefit from a multi-methodology test coverage. And in this respect, we start with testing its underlying purpose, that of the product import, for which we don't need browser interactions. This means that we can use a Kernel test.
Similar to how we wrote the previous test, we can start with the class like so (this time in the products module):
namespace Drupal\Tests\products\Kernel;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests the CSV Product Importer
*
* @group products
*/
class CsvImporterTest extends KernelTestBase {}
Nothing new so far.
Next, we need to specify the modules we need loaded. And here we have a bigger list:
/** * Modules to enable. * * @var array */ protected static $modules = ['system', 'csv_importer_test', 'products', 'image', 'file', 'user'];
Only the products module may seem obvious to you at this point, but all the rest are also needed. The system, image, file and user are all somehow needed for dealing with the file upload and storage process that is needed for the CsvImporter plugin.
But you may be wondering what's with the csv_importer_test module there. Often times you may need to create modules used only for the tests--most of the time because they contain some configuration you want to use in your testing. In our case, we did so to demonstrate where these modules would go and to add a products.csv test file that we can use in our tests.
Tests modules go inside the tests/modules folder of the module that contains the tests that use them. So in our case we have the csv_importer_test with its info.yml file:
name: CSV Importer Test description: Used for testing the CSV Importer core: 8.x type: module
And the mentioned CSV file we will use is right next to it:
id,name,number 1,Car,45345 2,Motorbike,54534
Now that we covered that, we can write the test method:
/**
* Tests the import of the CSV based plugin.
*/
public function testImport() {
$this->installEntitySchema('product');
$this->installEntitySchema('file');
$this->installSchema('file', 'file_usage');
$manager = $this->container->get('entity_type.manager');
$products = $manager->getStorage('product')->loadMultiple();
$this->assertEmpty($products);
$csv_path = drupal_get_path('module', 'csv_importer_test') . '/products.csv';
$csv_contents = file_get_contents($csv_path);
$file = file_save_data($csv_contents, 'public://simpletest-products.csv', FILE_EXISTS_REPLACE);
$config = $manager->getStorage('importer')->create([
'id' => 'csv',
'label' => 'CSV',
'plugin' => 'csv',
'plugin_configuration' => [
'file' => [$file->id()]
],
'source' => 'Testing',
'bundle' => 'goods',
'update_existing' => true
]);
$config->save();
$plugin = $this->container->get('products.importer_manager')->createInstanceFromConfig('csv');
$plugin->import();
$products = $manager->getStorage('product')->loadMultiple();
$this->assertCount(2, $products);
$products = $manager->getStorage('product')->loadByProperties(['number' => 45345]);
$this->assertNotEmpty($products);
$this->assertCount(1, $products);
}
The initial set-up here is a bit more complicated, partly because of Kernel tests not installing module schemas. Using the parent installEntitySchema() method we can install all the necessary tables for the Product and File content entities. However, since we are working with managed files, we also need to install the file_usage table manually. It is not technically an entity table. Again, there is no shame in arriving at these steps using trial and error.
Now that we have the basics set up, we do a sanity check and ensure that we don't have any product entities in the database. There is no reason why we should have any, but it doesn't hurt to ensure it. This guarantees a valid test since our goal will be to later assert the existence of products.
Then we create a managed File entity by using the products.csv file from the csv_importer_test module. The drupal_get_path() function is a very common way of retrieving the relative path to a module or a theme, regardless of where it is actually located. And we save the contents of this file into the public:// file system of the testing environment. Keep in mind, though, that after the test runs successfully, this file gets removed as Drupal cleans up after itself.
Next, we need to create an Importer configuration entity that uses the CSV based plugin to run the import. And instead of doing it through the UI, we do it programmatically. Using the storage manager we create the entity as we learned in Chapter 6, Data Modeling and Storage. Once we have that, we use the Importer plugin manager to create an instance based on this configuration entity (to which we gave the ID csv). And finally, we run the import of the products.
Now for the assertions, we do a double check. Since our test CSV contains two rows, we load all the Product entities again and assert that we have a total of two. No more, no less. And here we see another useful assertion method for working with arrays: assertCount(). But then we get a bit more specific and try to load a Product which has a field value (the number) equal to an expected number from the test CSV file. And assert that it is in fact found as well.
We could even do some more assertions. For example, we can check that all the Product field values have been set appropriately. I'll let you explore ways in which you can do this--either by querying based on these values or asserting equality between field values and their expected ones. But it's important to not go overboard as it will impact speed and in some cases add insufficient value to the test coverage to compensate for it. The trick is to find the right balance.
Finally, with our test in place, we can actually run it:
../vendor/bin/phpunit ../modules/custom/products/tests/src/Kernel/CsvImporterTest.php
But this time, we get an error that looks something like this:
RuntimeException: SplFileObject::__construct(http://localhost/vfs://root/sites/simpletest/25555814/files/simpletest-products.csv): failed to open stream: HTTP request failed! HTTP/1.0 404 Not Found
This is caused by the fact that in CsvImporter we try to instantiate a new \SplFileObject using the full URL of the file we get from the respective stream wrapper:
$url = $wrapper->getExternalUrl(); $spl = new \SplFileObject($url, 'r');
And although this worked when we wrote our functionality, and still does in the browser, in the test environment it doesn't. And that is because in the testing environment the file system is a virtual one (vfs) and uses a different base URL. However, the SplFileObject class can also be instantiated using the URI of the file. So all we have to do is change our code to this:
$spl = new \SplFileObject($file->getFileUri(), 'r');
And the test should now pass. And of course, the code will work the same way in the browser as well.