Writing PHP Tests - HL Vanilla Community
<main> <article class="userContent"> <div class="embedExternal embedImage display-large float-none"> <div class="embedExternal-content"> <a class="embedImage-link" href="https://us.v-cdn.net/6030677/uploads/MA14G4LFG3ZA/microsoftteams-image-288-29.png" rel="nofollow noreferrer noopener ugc" target="_blank"> <img class="embedImage-img" src="https://us.v-cdn.net/6030677/uploads/MA14G4LFG3ZA/microsoftteams-image-288-29.png" alt="MicrosoftTeams-image (8).png" height="108" width="1356" loading="lazy" data-display-size="large" data-float="none"></img></a> </div> </div> <p>Vanilla has numerous testing utilities to make automated testing easier.</p><p>A test will automatically be run in CI if it meets the following conditions.</p><ul><li>The filename matches the following pattern: <code class="code codeInline" spellcheck="false" tabindex="0">*Test.php</code>.</li><li>The classname matches the filename.</li><li>The class extends in some way <code class="code codeInline" spellcheck="false" tabindex="0">\PHPUnit\Framework\TestCase</code>.</li></ul><h2 data-id="base-classes">Base Classes</h2><p>Vanilla has a few recommended test cases to extend and offer setup utilities.</p><h3 data-id="vanillatestcase"><code class="code codeInline" spellcheck="false" tabindex="0">VanillaTestCase</code> </h3><p>Offers various additional assertions on top of the base PHPUnit <code class="code codeInline" spellcheck="false" tabindex="0">TestCase</code>. Very fast setup, but no database, routing, or container setup is provided.</p><h3 data-id="bootstraptestcase"><code class="code codeInline" spellcheck="false" tabindex="0">BootstrapTestCase</code></h3><p>Provides a full bootstrapping of Vanilla. Usually, you'll want to use this test case instead of the <code class="code codeInline" spellcheck="false" tabindex="0">MinimalContainerTestCase</code> since it more faithfully recreate's Vanilla's bootstrap.</p><h3 data-id="sitetestcase"><code class="code codeInline" spellcheck="false" tabindex="0">SiteTestCase</code></h3><p>If you subclass this test case then you'll get a full site install that you can test against. The site installed once for the class so make sure each test within the class work against the same install.</p><p>You can control the addons that are enabled with the install by overriding the <code class="code codeInline" spellcheck="false" tabindex="0">getAddons()</code> method.</p><h3 data-id="abstractapiv2test"><code class="code codeInline" spellcheck="false" tabindex="0">AbstractApiV2Test</code></h3><p>Sets up a "production-like" test harness, with full routing, database access, and utilities for starting sessions and calling APIv2 endpoints. Generally, you can usually use the <code class="code codeInline" spellcheck="false" tabindex="0">SiteTestCase</code> instead of this one.</p><h3 data-id="abstractresourcetest"><code class="code codeInline" spellcheck="false" tabindex="0">AbstractResourceTest</code></h3><p>Extend this to provide numerous default test assertions on an API endpoint. This test case expects a fully formed CRUD API conforming to vanilla norms. EG. POST, PATCH, GET, GET edit, DELETE, INDEX must all be defined. There are a few properties to configure.</p><p><strong>Example</strong></p><pre class="code codeBlock" spellcheck="false" tabindex="0">public static $addons = ['vanilla', 'subcommunities']; protected $baseUrl = '/subcommunities'; protected $resourceName = 'subcommunity'; protected $patchFields = ['name', 'folder', 'locale', 'categoryID', 'productID', 'isDefault', 'sort']; protected $testPagingOnIndex = false; protected $pk = 'subcommunityID'; </pre><h2 data-id="common-traits">Common Traits</h2><p>Vanilla provides various traits to make testing easier.</p><h3 data-id="bootstraptrait"><code class="code codeInline" spellcheck="false" tabindex="0">BootstrapTrait</code></h3><p>Used to bootstrap a <code class="code codeInline" spellcheck="false" tabindex="0">Garden\Container</code> instance with some good defaults. The container can be accessed with <code class="code codeInline" spellcheck="false" tabindex="0">self::container()</code> in order to insatiate classes that require dependency injection.</p><p>Notably this is used in <code class="code codeInline" spellcheck="false" tabindex="0">BootstrapTestCase</code>.</p><h3 data-id="sitetesttrait"><code class="code codeInline" spellcheck="false" tabindex="0">SiteTestTrait</code></h3><p>Use this trait if you want the site installed, but cant extend the <code class="code codeInline" spellcheck="false" tabindex="0">SitetestCase</code> class. This trait is used for tests that require starting addons. This is based on <code class="code codeInline" spellcheck="false" tabindex="0">BootstrapTrait</code> but adds on top the following utilities:</p><ul><li>Enable addons by overriding the <code class="code codeInline" spellcheck="false" tabindex="0">getAddons()</code> method.</li><li>Get an <code class="code codeInline" spellcheck="false" tabindex="0">HttpClient</code> instance for making calls to APIv2 with <code class="code codeInline" spellcheck="false" tabindex="0">$this->api()</code>.</li><li>Get something like an <code class="code codeInline" spellcheck="false" tabindex="0">HttpClient</code> for making calls to legacy controllers (<code class="code codeInline" spellcheck="false" tabindex="0">Gdn_Controllers</code>) with <code class="code codeInline" spellcheck="false" tabindex="0">$this->bessy()</code>.</li><li>Tests will run on an "installed" site with default configuration values applied.</li></ul><h2 data-id="testing-events">Testing Events</h2><div class="js-embed embedResponsive" data-embedjson="{"body":"The EventSpyTestTrait is used for tests that want to assert various events are fired. PSR Event Dispatcher The event manager allows firing of PSR-14 compliant events for newer things like ResourceEvents. Vanilla offers a few utilities to test them clearDispatchedEvents() - Clear any previously dispatched events.…","photoUrl":"https:\/\/us.v-cdn.net\/6030677\/uploads\/947\/TM5DAO1BWY5V.png","url":"https:\/\/success.vanillaforums.com\/kb\/articles\/352-eventspytesttrait","embedType":"link","name":"EventSpyTestTrait - Vanilla Success"}"> <a href="https://success.vanillaforums.com/kb/articles/352-eventspytesttrait" rel="nofollow noreferrer ugc"> https://success.vanillaforums.com/kb/articles/352-eventspytesttrait </a> </div><h2 data-id="creating-content-for-tests">Creating Content for Tests</h2><div class="js-embed embedResponsive" data-embedjson="{"body":"Vanilla has various traits for easily creating and testing data through API endpoints. These generally have a suffix ApiTestTrait. The expect to be used on test cases that use SiteTestTrait or AbstractApiV2Test. Structure of the Traits The traits generally all have the following in common. Easy createThing() methods for…","photoUrl":"https:\/\/us.v-cdn.net\/6030677\/uploads\/947\/TM5DAO1BWY5V.png","url":"https:\/\/success.vanillaforums.com\/kb\/articles\/354-api-utility-test-traits","embedType":"link","name":"API Utility Test Traits - Vanilla Success"}"> <a href="https://success.vanillaforums.com/kb/articles/354-api-utility-test-traits" rel="nofollow noreferrer ugc"> https://success.vanillaforums.com/kb/articles/354-api-utility-test-traits </a> </div><h2 data-id="testing-gdn_controllers">Testing Gdn_Controllers</h2><p>To test a Gdn_Controller, extends <code class="code codeInline" spellcheck="false" tabindex="0">AbstractAPIv2Test</code>. You can use the various API traits above as well as other calls to create your data.</p><p>Then you use bessy() in order to make assertions about pages.</p><p><strong>Testing Data Example</strong></p><pre class="code codeBlock" spellcheck="false" tabindex="0">/** * Editing a discussion should fill its form values. */ public function testGetCommentEdit(): void { \Gdn::session()->start($this->moderatorID); /** @var \PostController $r */ $r = $this->bessy()->get( '/post/edit-comment', ['commentID' => $this->comment['CommentID']] ); $this->assertEquals($this->comment['DiscussionID'], $r->Form->getValue('DiscussionID')); $this->assertSame($this->comment['Body'], $r->Form->getValue('Body')); } </pre><p><strong>Testing HTML example</strong></p><pre class="code codeBlock" spellcheck="false" tabindex="0">/** * Test run the test */ public function testGroupPage() { $this->createGroup(['name' => 'My Group']); $event1 = $this->createEvent(); $event2 = $this->createEvent([ 'name' => 'My Event <script>alert("Hello")</script>', ]); $html2 = $this->bessy()->getHtml("/group/{$this->lastInsertedGroupID}-my-group"); $html2->assertCssSelectorText('.Event:nth-of-type(2) .Title a', 'My Event <script>alert("Hello")</script>'); } </pre><h2 data-id="other-notable-utilities">Other Notable Utilities</h2><h3 data-id="mocking-dates-times">Mocking Dates & Times</h3><pre class="code codeBlock" spellcheck="false" tabindex="0">$mockedDataTime = CurrentTimeStamp::mockTime('Dec 24 2020'); $mockedDataTime = CurrentTimeStamp::mockTime(12124124); $mockedDataTime = CurrentTimeStamp::mockTime(new DateTime()); </pre><h3 data-id="wiping-a-database-table">Wiping a database table</h3><pre class="code codeBlock" spellcheck="false" tabindex="0">BootstrapTrait::resetTable('Discussion') </pre><p><br></p> </article> </main>