When learning about Kernel tests, we wrote a test for the CsvImporter that focused on the importing functionality given an existing Importer configuration entity (which we created programmatically). However, another important angle of this functionality is the process of creating this configuration entity as we are relying on Ajax to dynamically inject form elements related to the selected Importer plugin. So let's write a test for that as well.
Just as before, the test class can start with something like this:
namespace Drupal\Tests\products\FunctionalJavascript; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; /** * Testing the creation/edit of Importer configuration entities using the CSV importer * * @group products */ class ImporterFormTest extends WebDriverTestBase {}
And like always, let's enable some modules:
/** * Modules to enable. * * @var array */ protected static $modules = ['image', 'file', 'node'];
You may be wondering why, for example, the products module is not in that list. At the time of writing, it did not work, as a dependency-related error was being thrown when enabling it (missing plugin defined by the image module). So instead, we can also enable modules directly in our test or setUp() methods. And that is exactly what we will do.
Even though we only write one test method, there is quite a bit of preparation for it that we might want to reuse elsewhere. Plus, it also looks cleaner to be separated from the actual test method. So we can add it to the setUp() method instead:
/** * {@inheritdoc} */ public function setUp() { parent::setUp(); $this->container->get('module_installer')->install(['products', 'csv_importer_test']); $csv_path = drupal_get_path('module', 'csv_importer_test') . '/products.csv'; $csv_contents = file_get_contents($csv_path); $this->file = file_save_data($csv_contents, 'public://simpletest-products.csv', FileSystemInterface::EXISTS_REPLACE); $this->admin = $this->drupalCreateUser(['administer site configuration']); $this->bundle = ProductType::create(['id' => 'goods', 'label' => 'Goods']); $this->bundle->save(); }
And the new use statement:
use Drupal\products\Entity\ProductType; use Drupal\Core\File\FileSystemInterface;
As expected, the first thing we do is install the products and csv_importer_test modules. We use the ModuleInstaller service for that. Then, we do the same thing as we did in the previous test—load the test CSV file from the csv_importer_test module and "upload" it to Drupal creating a new managed File entity.
Then, we create an administrator user account that has the permission needed for creating Importer configuration entities, as well as a bundle for the Product entity so that we can actually create products. We didn't need to worry about the bundle in the previous test because we created the Importer configuration programmatically. But now, through the UI, a bundle needs to exist in order to select it.
The resulting File entity, admin user account and ProductType configuration entity we store on class properties so we should also define those:
/** * @var \Drupal\file\FileInterface */ protected $file; /** * @var \Drupal\Core\Session\AccountInterface */ protected $admin; /** * @var \Drupal\products\Entity\ProductType */ protected $bundle;
And with this we are ready to write our empty test method and start filling it up step by step:
/** * Tests the importer form. */ public function testForm() {}
We can start with the basics:
$this->drupalGet('/admin/structure/importer/add'); $assert = $this->assertSession(); $assert->pageTextContains('Access denied');
We navigate to the form for creating importer configuration entities and assert that the user does not have access. This is because by default we are browsing as anonymous users. Next, we need to log in and try this again:
$this->drupalLogin($this->admin); $this->drupalGet('/admin/structure/importer/add'); $assert->pageTextContains('Add importer'); $assert->elementExists('css', '#edit-label'); $assert->elementExists('css', '#edit-plugin'); $assert->elementExists('css', '#edit-update-existing'); $assert->elementExists('css', '#edit-source'); $assert->elementExists('css', '#edit-bundle'); $assert->elementNotExists('css', 'input[name="files[plugin_configuration_plugin_file]"]');
We use the same drupalLogin() method and navigate back to the form. This time we assert that we have the title as well as various HTML elements—the form elements used for creating the entity. Moreover, we also assert that we do not have the element for uploading the CSV file because that should only show up if we select that we want to use the CSV Importer plugin.
It follows we do just that:
$page = $this->getSession()->getPage(); $page->selectFieldOption('plugin', 'csv'); $this->assertSession()->assertWaitOnAjaxRequest(); $assert->elementExists('css', 'input[name="files[plugin_configuration_plugin_file]"]');
Using the getSession() method, we get the current Mink session, from which we can get the object representing the actual page we are looking at. This is a DocumentElement object which can be traversed, inspected and manipulated in all sorts of ways. I recommend you check out the TraversableElement class for all the available methods.
One such method is selectFieldOption() by which we can specify the locator of an HTML select element (ID, name or label) and a value, and it will trigger the selection. As you know, this is supposed to make an Ajax request bringing in our new form elements. And using assertWaitOnAjaxRequest() on the JSWebAssert object, we can wait until that is complete. Finally, we can assert that the file upload field is present on the page.
Next, we proceed with filling in the form:
$page->fillField('label', 'Test CSV Importer'); $this->assertJsCondition('jQuery(".machine-name-value").html() == "test_csv_importer"'); $page->checkField('update_existing'); $page->fillField('source', 'testing'); $page->fillField('bundle', $this->bundle->id()); $wrapper = $this->container->get('stream_wrapper_manager')->getViaUri($this->file->getFileUri()); $page->attachFileToField('files[plugin_configuration_plugin_file]', $wrapper->realpath()); $this->assertSession()->assertWaitOnAjaxRequest(); $page->pressButton('Save'); $assert->pageTextContains('Created the Test CSV Importer Importer.');
The generic fillField() method is useful for things like text fields while the checkField() method is expectedly useful for checkboxes. The locator for both is again either the ID, the name or the label of the element.
We also use the assertJsCondition method to have the execution wait until a JavaScript change has happened on the page. And we do this to ensure that the entity machine name field has been currently filled in.
Next, with the help of the stream wrapper of the file that we uploaded, and more specifically its realpath() method, we attach the file to the field using the attachFileToField() method. This triggers an Ajax request, which again we wait for to complete. Lastly, we use the pressButton() method to click on the submit button and then assert that we have a confirmation message printed out (the form has been saved and the page refreshed).
Now to check that the operation actually went through properly:
$config = Importer::load('test_csv_importer'); $this->assertInstanceOf('Drupal\products\Entity\ImporterInterface', $config); $fids = $config->getPluginConfiguration()['file']; $fid = reset($fids); $file = File::load($fid); $this->assertInstanceOf('Drupal\file\FileInterface', $file);
And the new use statements:
use Drupal\file\Entity\File; use Drupal\products\Entity\Importer;
We load the configuration entity using the ID we gave it and then assert that the resulting object is an instance of the correct interface. This checks we actually did save the entity. Next, we load the File entity based on the ID found in the Importer configuration entity and assert that it itself also implements the correct interface. This proves that the file actually got saved and the configuration is correct.
Instead of checking the rest of the field values programmatically, in the same way, we opt for navigating to the edit form of the Importer entity and asserting that the values are pre-filled correctly:
$this->drupalGet('admin/structure/importer/test_csv_importer/edit'); $assert->pageTextContains('Edit Test CSV Importer'); $assert->fieldValueEquals('label', 'Test CSV Importer'); $assert->fieldValueEquals('plugin', 'csv'); $assert->checkboxChecked('update_existing'); $assert->fieldValueEquals('source', 'testing'); $page->hasLink('products.csv'); $bundle_field = $this->bundle->label() . ' (' . $this->bundle->id() . ')'; $assert->fieldValueEquals('bundle', $bundle_field);
The fieldValueEquals() and checkboxChecked() methods are handy for checking field values. Moreover, we also use the hasLink() method to check whether there is a link with that name on the page. This is actually to prove the uploaded file is shown correctly:
And finally, since the bundle field is a reference field and not a simple text field, we need to construct the value the testing framework actually sees there and which is in this pattern: Label (ID).
And with this, our test is complete and we can run it in its entirety:
../vendor/bin/phpunit ../modules/custom/products/tests/src/Kernel/CsvImporterTest.php