-
Using Data Providers in PHPUnit Tests
This is the before code, where a single test is run, and each scenario I’m testing could be influenced by the previous scenario (not a good thing, unless that was my goal, which it was not).
123456789101112131415161718192021/*** Tests the getRegistrationsWithinNumDays function.*/public function testGetRegistrationsWithinNumDays() {$firstRegDateTimeStr = '2024-03-18 08:00:00';$this->mhpreregTestHelpers->createRegistration(['field_reg_datetime' => '2024-03-18 09:00:00']);$this->mhpreregTestHelpers->createRegistration(['field_reg_datetime' => '2024-03-19 13:00:00']);$this->mhpreregTestHelpers->createRegistration(['field_reg_datetime' => '2024-03-20 14:20:00']);$this->mhpreregTestHelpers->createRegistration(['field_reg_datetime' => '2024-03-20 17:35:00']);$this->mhpreregTestHelpers->createRegistration(['field_reg_datetime' => '2024-03-21 17:35:00']);$this->mhpreregTestHelpers->createRegistration(['field_reg_datetime' => '2024-03-22 08:00:00']);$registrations = Utils::getIdsOfRegistrationsWithinNumDays(1, $firstRegDateTimeStr);$this->assertEquals(2, count($registrations));$registrations = Utils::getIdsOfRegistrationsWithinNumDays(2, $firstRegDateTimeStr);$this->assertEquals(3, count($registrations));$registrations = Utils::getIdsOfRegistrationsWithinNumDays(3, $firstRegDateTimeStr);$this->assertEquals(5, count($registrations));}This is the after code, where three tests are run. Unfortunately this takes 3x longer to execute. The upside is that each scenario cannot affect the others and it’s perhaps more readable and easier to add additional scenarios.
123456789101112131415161718192021222324252627/*** Data provider for testGetRegistrationsWithinNumDays.*/public function registrationsDataProvider() {return [// Label => [numDays, expectedCount].'1 day' => [1, 2],'2 days' => [2, 3],'3 days' => [3, 5],];}/*** Tests the getRegistrationsWithinNumDays function using a data provider.** @dataProvider registrationsDataProvider*/public function testGetRegistrationsWithinNumDays($numDays, $expectedCount) {$this->mhpreregTestHelpers->createRegistration(['field_reg_datetime' => '2024-03-18 08:00:00']);$this->mhpreregTestHelpers->createRegistration(['field_reg_datetime' => '2024-03-19 09:00:00']);$this->mhpreregTestHelpers->createRegistration(['field_reg_datetime' => '2024-03-20 13:00:00']);$this->mhpreregTestHelpers->createRegistration(['field_reg_datetime' => '2024-03-21 14:20:00']);$this->mhpreregTestHelpers->createRegistration(['field_reg_datetime' => '2024-03-21 17:35:00']);$this->mhpreregTestHelpers->createRegistration(['field_reg_datetime' => '2024-03-22 17:35:00']);$registrations = Utils::getIdsOfRegistrationsWithinNumDays($numDays, '2024-03-18 08:00:00');$this->assertEquals($expectedCount, count($registrations), "Failed for {$numDays} days.");} -
Testing Cookie Modification in Laravel 8
If there’s a better way to pass a cookie from one request to another, in a phpunit feature test in Laravel, please let me know! Here’s one way to handle it:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546<?phpnamespace Tests\Feature;use Illuminate\Cookie\CookieValuePrefix;use Tests\TestCase;class DiveDeeperTest extends TestCase{/*** Tests if the diveDeeper route appends the passed value to the cookie arr.* @return void*/public function test_it_updates_path_cookie(){// Post a deeperPath to the diveDeeper route (with a starter cookie).// This should append the "deeperPath" value to the "path" cookie array.$resp = $this->withCookie('path', serialize(['/']))->post(route('diveDeeper'), ['deeperPath' => 'Users']);// Assert that the cookie has been modified.$resp->assertCookie('path', serialize(['/', 'Users']));// If we make another post request here, it won't include our// modified "path" cookie. We have to retrieve it, then pass it// through to the next request.// Decrypt the cookie (now modified by the diveDeeper route).$cookieValue = CookieValuePrefix::remove(app('encrypter')->decrypt($resp->getCookie('path')->getValue(), false));// Hit the diveDeeper route again using the modified cookie value.$resp = $this->withCookie('path', $cookieValue)->post(route('diveDeeper'), ['deeperPath' => 'adam']);// Assert that the cookie has been modified again.$resp->assertCookie('path', serialize(['/', 'Users', 'adam']));// Etc...}} -
Setting Drupal 8 Node Properties with Behat
The title of this post could also have been “Opening and Closing a Details Element with Behat” or “Clicking Any Element with Behat”.
The Drupal 8 node add/edit screen has a number properties on the right side of the screen. I wanted to use Behat to click the “Provide a menu link” checkbox. On page load this MENU SETTINGS pane is closed like the others.
Here’s an example:
The “URL SETTINGS”, “MENU SETTINGS”, actually mask a <details> element.
The “MENU SETTINGS” markup looks like this:
I expected to be able to write a few lines of Behat like this:
1234When I am at "/node/add/page"Then I should see the heading "Create Page" in the "header" regionThen I fill in "Title" with "Landing Page without Sidebar"And I check "Provide a menu link"Unfortunately this results in the failure: element not interactable.
So, I thought I may need to first click to open the MENU SETTINGS pane so that I can see the checkbox before I try to click it. I modified my code to look like this:
12345When I am at "/node/add/page"Then I should see the heading "Create Page" in the "header" regionThen I fill in "Title" with "Landing Page without Sidebar"And I click "Menu settings"And I check "Provide a menu link"Unfortunately this results in the failure: Link with id|title|alt|text “Menu settings” not found.
I tried using the edit-menu id, “MENU SETTINGS” in all caps, etc. but it always resulted in the same “Link with …”
I realized the click step is only looking for links.
Solution
I added a new step definition to my features/bootstrap/FeatureContext.php file:
1234567891011121314151617/*** Click any element.** @Given I click the :selector element** @see https://stackoverflow.com/a/33672497/1023773*/public function iClickTheElement($selector) {$page = $this->getSession()->getPage();$element = $page->find('css', $selector);if (empty($element)) {throw new Exception("No html element found for selector '{$selector}'");}$element->click();}
12345When I am at "/node/add/page"Then I should see the heading "Create Page" in the "header" regionThen I fill in "Title" with "Landing Page without Sidebar"And I click the "#edit-menu" elementAnd I check "Provide a menu link"