In the previous article in this series, we looked at helper methods in Codeception. In this one we'll see how to write integration test code for processors.

This article assumes that you've installed and configured Codeception and PhpUnit and created the classes and unit tests described in the previous articles. The test files and classes are available at GitHub here.
The modUserCreateProcessor Class
The processor we're testing is located here:
core/model/modx/processors/security/user/create.class.php
If you look at that class with an eye to testing, you'll see that it's extraordinarily difficult to test. Its various methods are very tightly coupled and there is little or no dependency injection. It clearly wasn't developed using Test-driven Development (TDD).
This test class is a good example of developing tests for legacy code. We're able to test most of the methods, but in doing so, we often have to call other methods of the class that shouldn't need to be called. (We've skipped some methods to keep down the length of this article.) That's a clear sign of a lack of dependency injection. We should be able to test a method in isolation, but in most cases we can't. We can't even use mocks or stubs because we actually need the other methods to execute.
Another issue is that much of the work of this class is performed in the classes it extends. For example, except for the userGroup
methods, there's no save()
method that actually saves the user object and/or its attached user profile object. That method is hidden in one of the parent::...
calls, but it's difficult to guess which one.
Background on MODX Users
In MODX, the modUser
object is very light. It stores the username, password, active status, and a few other fields (listed here). That information is stored in the 'modx_users' table. (Actually, the modx_
prefix is the default. It can be changed during setup).
The other user data is stored in the modUserProfile
table, which holds the email, fullname, phone, address, gender, and some other fields (listed here).
The modUserCreateProcessor
creates and saves both the modUser
object and its associated modUserProfile
object, which complicates our test somewhat.
The T10_UserProcessorTest.php File
To create the test, give this command from the _build
directory:
codecept generate:test integration T10_UserProcessor
Open that file in your editor and replace everything with this code (also available at GitHub here):
<?php use Codeception\Util\Fixtures; class T10UserProcessorTest extends \Codeception\Test\Unit { /** * @var \IntegrationTester */ protected $tester; protected $processor; /** @var modX $modx */ public $modx; protected $fields = array( 'username' => 'Joe99', 'password' => 'somepassword', 'passwordnotifymethod' => 'x', 'email' => 'joe99@hotmail.com', ); protected function _before() { require_once 'C:/xampp/htdocs/test/core/model/modx/modProcessor.class.php'; /* Get test file from dev. environment */ require_once 'C:/xampp/htdocs/test/core/model/modx/processors/security/user/create.class.php'; $this->modx = Fixtures::get('modx'); assertInstanceOf('modX', $this->modx); $this->processor = modUserCreateProcessor::getInstance($this->modx, 'modUserCreateProcessor', $this->fields); assertInstanceof('modUserCreateProcessor', $this->processor); $this->tester->removeUsers($this->modx, array('Joe99')); } protected function _after() { $this->tester->removeUsers($this->modx, array('Joe99')); } public function testClassKey() { $p = $this->processor; assertEquals('modUser', $p->classKey); } public function testInitialize() { /** @var modUserCreateProcessor $p */ $p = $this->processor; $p->initialize(); assertInstanceOf('modUser', $p->object); $props = array( 'class_key' => 'modUser', 'blocked' => false, 'active' => false, ); foreach ($props as $prop => $value) { assertEquals($value, $p->getProperty($prop)); } $fields = array( 'objectType' => 'user', 'beforeSaveEvent' => 'OnBeforeUserFormSave', 'afterSaveEvent' => 'OnUserFormSave', ); foreach( $fields as $field => $value) { assertTrue($p->$field == $value); } } public function testBeforeSave() { /** @var modUserCreateProcessor $p */ $p = $this->processor; $p->initialize(); $result = $p->beforeSave(); assertTrue($result, print_r($result, true)); $object = $p->object; assertInstanceOf('modUser', $object); $username = $object->get('username'); assertEquals('Joe99', $username); $profile = $object->getOne('Profile'); assertInstanceOf('modUserProfile', $profile); $email = $profile->get('email'); assertEquals('joe99@hotmail.com', $email); } public function testAddProfile() { /** @var modUserCreateProcessor $p */ $p = $this->processor; $p->initialize(); $profile = $p->addProfile(); assertInstanceOf('modUserProfile', $profile); $object = $p->object; assertInstanceOf('modUser', $object); $profile = $object->getOne('Profile'); assertInstanceOf('modUserProfile', $profile); $email = $profile->get('email'); assertEquals('joe99@hotmail.com', $email); } public function testAfterSave() { $I = $this->tester; /** @var modUserCreateProcessor $p */ $p = $this->processor; $p->initialize(); $p->process(); $result = $p->afterSave(); assertTrue($result); $userTable = $this->_getTableName('modUser'); $profileTable = $this->_getTableName('modUserProfile'); $I->seeInDatabase($userTable, array('username' => 'Joe99')); $I->seeInDatabase($profileTable, array('email' => 'joe99@hotmail.com')); } /** Test full processor action with runProcessor() */ public function testFull() { $I = $this->tester; $fields = $this->fields; /* Bypass MODX bug */ $options = array('processors_path' => 'C:/xampp/htdocs/test/core/model/modx/processors/security/'); $result = $this->modx->runProcessor('user/create', $fields, $options); assertInstanceOf('modProcessorResponse', $result); $userTable = $this->_getTableName('modUser'); $profileTable = $this->_getTableName('modUserProfile'); $I->seeInDatabase($userTable, array('username' => 'Joe99')); $I->seeInDatabase($profileTable, array('email' => 'joe99@hotmail.com')); } /** * Utility function to remove back-ticks from table name * * @param $class * @return string */ public function _getTableName($class) { $tableName = $this->modx->getTableName($class); return str_replace('`', '', $tableName); } }
Notes on the Code
If you've been following along in these articles, the code should be fairly self-explanatory, but a few parts need a little discussion.
The first is the tablename stuff. In order to use our seeInDatabase()
tests, we need to give the table name, but the actual name of the table will depend on what the user chose for the table prefix when running setup. The default prefix is modx_
, so the typical table name for the MODX User object (modUser
is the modx_users
table, but if the user has chosen another prefix, it will be named something else.
To help with this, MODX (which knows the prefix) provides the getTableName('classname;)
method. There's still a hitch, though getTableName()
assumes that you're going to use the table name in a query, so it returns the name surrounded by backticks, making it unusable by seeInDatabase()
. So we use this code in our tests:
$userTable = $this->_getTableName('modUser'); $profileTable = $this->_getTableName('modUserProfile'); $I->seeInDatabase($userTable, array('username' => 'Joe99')); $I->seeInDatabase($profileTable, array('email' => 'joe99@hotmail.com'));
The _getTableName
function is our own utility function that calls MODX's getTableName()
, removes the back-ticks, and returns the result, which we can then use in seeInDatabase()
.
We hard-coded the table names in an earlier integration test, which should have worked fine for you because you left the default prefix alone during setup, but for tests in a real project where some users may have used a different prefix for their MODX install, getting the correct table name is essential. Feel free to go back and add the correct process to the T8_UserDbTest.php
file.
The second quirk is in this code:
/* Bypass MODX bug */ $options = array('processors_path' => 'C:/xampp/htdocs/test/core/model/modx/processors/security/'); $result = $this->modx->runProcessor('user/create', $fields, $options);
The MODX class runProcessor()
method calls include_once
to load the processor class, which is located under the processors
directory at security/user/create.class.php
. Normally, we'd call $modx->runProcessor('security/user/create'). The processor class returns its own name, modUserCreateProcessor
the first time we include it, so that works fine if we only call it once.
Unfortunately, we've already included that class earlier in our tests. On subsequent calls, include_once
returns 1
to indicate that the class is already included rather than returning the class name. MODX is smart enough to detect that return, but it guesses the class name based on the path we send in the first parameter. That means it creates a modSecurityUserCreateProcessor
, which is not actually the name of our class and things then fall apart. This only happens because the class file is in a subdirectory rather than directly under the processors
directory.
The workaround for this bug is to send the processors_path
in the $options
array (see the code just above). We've added the security/
directory to the end of the processors path, then called runProcessor()
with user/create
. MODX finds that class file by tacking on user/create.class.php
to the end of the path we sent in the options array. That works. It also guesses the name of the class based on the first parameter, user/create
, so we've fooled it into getting the class name right. This bug is fixed in MODX 3, but files are moved around in that version, so some of our tests would have to be modified to have the correct paths to run in MODX 3.
Our test above is incomplete in various ways. If it were a full test, it would be much longer. We'd have to create and remove user groups to test the method that handles adding users to user groups. We'd also have to add quite a few extra lines of code (and possibly a data provider) to make sure the processor handles invalid user data properly. Adding those tests is left as an exercise for the reader.
Wrapping Up
That's it for the unit and integration tests. At this point, if you've created all the tests and classes we've written about, you should be able to run them all in one pass by giving this command in the _build
directory.
codecept run unit,integration
You should see something like this at the end of the run:
Time: 3.64 seconds, Memory: 16.00 MB OK (135 tests, 382 assertions)
If you scroll up and look at the lists, you'll see that Codeception runs the tests in each suite in alphabetical order. That's why the T10_
tests run ahead of the other numbered integration tests (T1 comes ahead of T6 in the alphabet).
If you add this line to the settings:
section of codeception.yml
in the _build
directory, Codeception will run the test files in random order:
shuffle: true
Running several times with the shuffle option on is a good way to make sure none of the tests depend on things that happen in other tests. All software tests should be independent of one another.
Coming Up
In the next article, we'll take a brief look at functional testing.
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.