Acceptance Testing XV - Skeleton Resource Protection Test

A basic skeleton for a test of the MODX security system for protecting resources

In the previous two articles, we created a new page object containing the CSS and XPath locators we need for our test, and looked at the relevant HTML code for them. In this one we'll create a skeleton version of our actual test. It will include calls to the Objects class methods that create and remove all the MODX objects used in our test as well as comments describing the steps the test will take.

MODX logo

This article assumes that you've installed and configured Composer, Codeception, PhpUnit, MODX, Java, WebDriver, and ChromeDriver as described in earlier articles, and that you've created the acceptance test support files from those articles.

Skeleton Resource Protection Test

In the _build directory, enter this command:

codecept generate:cest acceptance T16_ResourceProtection

The initial tests in this article will only work in MODX 2. I created them using MODX 2.8.1, but they should work with any recent 2.x.x version.

Test Review

In our test, we'll create three users: an admin super user (JoeTester), a user who should see the protected pages (PrivateUser), and a user who should not (PublicUser). We'll also create corresponding user groups (PrivateUsers and PublicUsers, a role for those users (TestRole), a protected resource (PrivateResource) and an unprotected resource (PublicResource). We'll also create two corresponding resource groups (PrivateResources and PublicResources), and finally, an ACL entry to protect all resources in the "PrivateResources" resource group (in other words, the "Private" resource).

Once the objects all exist, we'll test the ACL process by logging in the admin user to create the ACL entry that protects the hidden resource, then log out the admin, and log in each of the non-admin users to see if the ACL entry works as it should.

We already have a Login PageObject that can log the users in and out, and we were wise enough to let it log in any user if we send it the user's username and password. It would probably be more appropriate for that task to be done by a StepObject, but we have enough to do. Feel free to create a Login StepObject later.

At the moment, we have code to create our objects, but we have no way to run it. It's time to create a skeleton version of our test and add code to it that will create the objects.

In the _build directory, enter this command:

codecept generate:cest acceptance T16_ResourceProtection

Use this code for the file (also available at GitHub here.

use Codeception\Util\Fixtures;
use Page\Acceptance\LoginPage;
use Page\Acceptance\ResourceTestPage;

class T16_ResourceProtectionCest
    /** @var _generated\modX $modx */
    public $modx;
    private const ROLES = array('TestUser');
    private const USER_GROUPS = array('PublicUsers',
    private const RESOURCE_GROUPS =
        array('PublicResources', 'PrivateResources');

    public static function _before(\Step\Acceptance\Objects $I) {
        /* Load data files */
        $users = include codecept_data_dir() .

        $resources = include codecept_data_dir() .

        $modx = Fixtures::get('modx');
        assertTrue($modx instanceof modX);
        $I->createRoles($modx, self::ROLES);
        $I->createUserGroups($modx, self::USER_GROUPS);
        $I->createUsers($modx, $users);
        $I->createResourceGroups($modx, self::RESOURCE_GROUPS);
        $I->createResources($modx, $resources);

    public static function _after(\Step\Acceptance\Objects $I) {
     // return;  /* allows examination of objects and ACL */

        $users = include codecept_data_dir() .
        $resources = include codecept_data_dir() .
        $modx = Fixtures::get('modx');
        assertTrue($modx instanceof modX);
        $I->removeRoles($modx, self::ROLES);
        $I->removeUserGroups($modx, self::USER_GROUPS);
        $I->removeUsers($modx, $users);
        $I->removeResourceGroups($modx, self::RESOURCE_GROUPS);
        $I->removeResources($modx, $resources);

    public function ResourceProtectionTest(AcceptanceTester $I)
        $testPage = new ResourceTestPage($I);
        $wait = 2;

        /* Login admin user JoeTester */

        /* *** Create ACL entry *** */

            /* Go to ACL panel */

            /* Update PrivateUser user group */

            /* Select Permissions top tab */

            /* Select Resource Group Access left tab */

            /* Create actual ACL entry */

            /* Set Resource Group */

            /* Set Context */

            /* Set Role */

            /* Set Policy */

            /* Save ACL entry */

        /* Make sure JoeTester can see PublicResource
           and can't see PrivateResource */

        /* Logout JoeTester */

        /* Login PrivateUser */

        /* Make sure Private resource is visible */

        /* Logout PrivateUser */

        /* Login PublicUser */

        /* Make sure Private resource is not visible */

        /* Logout PublicUser */


Our T16 test should run now, although it won't do anything but create and then remove the objects used in our test. If you'd like to verify that, you can uncomment the return statement in the test's _after() method. That will leave the objects in place in the manager where you can examine them. Don't forget to restore the comment after checking them out.

Code Review

The test method (ResourceProtectionTest()) is empty except for the comments, but the code in the _before() method creates all the objects needed for our test. The code in the _after() method removes them all.

There are only two assertions in the test itself. They check to make sure the $modx variable is an instance of the modX class before it's passed to any of the methods in the StepObject class. There are also a number of assertions in the Objects.php step object (where the objects are actually created and removed). Those assertions make sure the objects were created successfully.

There could also be tests that make sure the objects are removed, but since we're not changing any of the objects and we know they objects exist, the removal is very unlikely to fail and would slow down the test each time it's run.

The _before() and _after() will run before and after each test method in the file. This could cost a lot of time if we had a number of test methods, but since there is only one test method, it works fine.

Codeception extends PhpUnit, so we should be able to use PhpUnit's setUpBeforeClass(), which runs once before any tests are run, and tearDownAfterClass(), which (in theory) runs once after the last test method is completed. Unfortunately, I couldn't make them work in Codeception. The tearDownAfterClass() method runs *before* any test methods and removes all the objects before the actual testing starts.

Many testing purists would argue that everything should be brought back to its initial state after each test method anyway. I think a case can be made for being able to set up a state before all tests and undo it at the end if that capability is used carefully.

For example, in a test like ours where the same set of objects are used in all tests and the objects themselves are never modified, there should be no harm in creating them once at the beginning of the tests and removing them after the last test.


You'll see this code in both the _before() and _after() methods:

$users = include codecept_data_dir() . '/user_data.php';
$resources = include codecept_data_dir() . '/resource_data.php';

The codecept_data_dir() function will return the path to the data directory. This path is set in the master _build/codeception.yml file:

    tests: tests
    output: tests/_output
    data: tests/_data

If your test won't run because this is not set or not correct, you can either set it correctly, or just hard-code the path in the _before() and _after() methods.


You'll see something like this often in Codeception examples:

$I->wantTo('Do Something');

It's essentially a comment. Codeception doesn't do anything with it unless there is a failure in the test and you are running from the command line. In that case it displays the text from the closest $I->wantTo() before the failure.

If you are running your test inside PhpStorm, you won't see the $I->wantTo() text at all. On the other hand, PhpStorm will show you the test methods that succeeded and failed, and will put a link in any error message that will let you jump to the line containing the failed assertion. PhpStorm will also let you step through your code in the debugger and show you the values of all variables along the way.

I generally prefer PHP comments like the ones in the example above rather than the $I->wantTo() lines, since highlighting makes the comments easier to see. There's nothing wrong with doing both types of commenting if that's your preference.

Coming Up

In the next article, we'll fill in the code for the finished tests.

For more information on how to use MODX to create a web site, see my web site Bob's Guides, or better yet, buy my book: MODX: The Official Guide.

Looking for high-quality, MODX-friendly hosting? As of May 2016, Bob's Guides is hosted at A2 hosting. (More information in the box below.)

Comments (0)

Please login to comment.