diff --git a/module/VuFind/src/VuFindTest/Unit/UserCreationTrait.php b/module/VuFind/src/VuFindTest/Unit/UserCreationTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..d87708c19c7b7f0de42c1fa397fb91bb077043ec
--- /dev/null
+++ b/module/VuFind/src/VuFindTest/Unit/UserCreationTrait.php
@@ -0,0 +1,95 @@
+<?php
+
+/**
+ * Trait with utility methods for user creation/management. Assumes that it
+ * will be applied to a subclass of DbTestCase.
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  Tests
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:unit_tests Wiki
+ */
+namespace VuFindTest\Unit;
+
+/**
+ * Trait with utility methods for user creation/management. Assumes that it
+ * will be applied to a subclass of DbTestCase.
+ *
+ * @category VuFind2
+ * @package  Tests
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:unit_tests Wiki
+ */
+trait UserCreationTrait
+{
+    /**
+     * Static setup support function to fail if users already exist in the database.
+     * We want to ensure a clean state for each test!
+     *
+     * @return mixed
+     */
+    protected static function failIfUsersExist()
+    {
+        // If CI is not running, all tests were skipped, so no work is necessary:
+        $test = new static();   // create instance of current class
+        if (!$test->continuousIntegrationRunning()) {
+            return;
+        }
+        // Fail if there are already users in the database (we don't want to run this
+        // on a real system -- it's only meant for the continuous integration server)
+        $userTable = $test->getTable('User');
+        if (count($userTable->select()) > 0) {
+            return self::fail(
+                'Test cannot run with pre-existing user data!'
+            );
+        }
+    }
+
+    /**
+     * Static teardown support function to destroy user accounts. Accounts are
+     * expected to exist, and the method will fail if they are missing.
+     *
+     * @param array|string $users User(s) to delete
+     *
+     * @return void
+     *
+     * @throws \Exception
+     */
+    protected static function tearDownUsers($users)
+    {
+        // If CI is not running, all tests were skipped, so no work is necessary:
+        $test = new static();   // create instance of current class
+        if (!$test->continuousIntegrationRunning()) {
+            return;
+        }
+
+        // Delete test user
+        $userTable = $test->getTable('User');
+        foreach ((array)$users as $username) {
+            $user = $userTable->getByUsername($username, false);
+            if (empty($user)) {
+                throw new \Exception('Problem deleting expected user.');
+            }
+            $user->delete();
+        }
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/tests/integration-tests/src/VuFindTest/Auth/DatabaseTest.php b/module/VuFind/tests/integration-tests/src/VuFindTest/Auth/DatabaseTest.php
index 522dccc205ad0c2638d3e1648deae86914bdceb0..6ad3e11ebcd552f1e0778a0ab619b0fa6a89d0f3 100644
--- a/module/VuFind/tests/integration-tests/src/VuFindTest/Auth/DatabaseTest.php
+++ b/module/VuFind/tests/integration-tests/src/VuFindTest/Auth/DatabaseTest.php
@@ -39,6 +39,8 @@ use VuFind\Auth\Database;
  */
 class DatabaseTest extends \VuFindTest\Unit\DbTestCase
 {
+    use \VuFindTest\Unit\UserCreationTrait;
+
     /**
      * Object to test
      *
@@ -49,23 +51,11 @@ class DatabaseTest extends \VuFindTest\Unit\DbTestCase
     /**
      * Standard setup method.
      *
-     * @return void
+     * @return mixed
      */
     public static function setUpBeforeClass()
     {
-        // If CI is not running, all tests were skipped, so no work is necessary:
-        $test = new DatabaseTest();
-        if (!$test->continuousIntegrationRunning()) {
-            return;
-        }
-        // Fail if there are already users in the database (we don't want to run this
-        // on a real system -- it's only meant for the continuous integration server)
-        $userTable = $test->getTable('User');
-        if (count($userTable->select()) > 0) {
-            return self::markTestSkipped(
-                'Test cannot run with pre-existing user data!'
-            );
-        }
+        return static::failIfUsersExist();
     }
 
     /**
@@ -291,19 +281,6 @@ class DatabaseTest extends \VuFindTest\Unit\DbTestCase
      */
     public static function tearDownAfterClass()
     {
-        // If CI is not running, all tests were skipped, so no work is necessary:
-        $test = new DatabaseTest();
-        if (!$test->continuousIntegrationRunning()) {
-            return;
-        }
-
-        // Delete test user
-        $test = new DatabaseTest();
-        $userTable = $test->getTable('User');
-        $user = $userTable->getByUsername('testuser', false);
-        if (empty($user)) {
-            throw new \Exception('Problem deleting expected user.');
-        }
-        $user->delete();
+        static::tearDownUsers('testuser');
     }
 }
\ No newline at end of file
diff --git a/module/VuFind/tests/integration-tests/src/VuFindTest/Auth/ILSTest.php b/module/VuFind/tests/integration-tests/src/VuFindTest/Auth/ILSTest.php
index 20e5b71c0b058d3b41bdd190b3a165e0a07c5d92..696102a8566adb453ccec70d6970212090702898 100644
--- a/module/VuFind/tests/integration-tests/src/VuFindTest/Auth/ILSTest.php
+++ b/module/VuFind/tests/integration-tests/src/VuFindTest/Auth/ILSTest.php
@@ -39,6 +39,8 @@ use VuFind\Auth\ILS, VuFind\Db\Table\User;
  */
 class ILSTest extends \VuFindTest\Unit\DbTestCase
 {
+    use \VuFindTest\Unit\UserCreationTrait;
+
     /**
      * Object to test
      *
@@ -56,23 +58,11 @@ class ILSTest extends \VuFindTest\Unit\DbTestCase
     /**
      * Standard setup method.
      *
-     * @return void
+     * @return mixed
      */
     public static function setUpBeforeClass()
     {
-        // If CI is not running, all tests were skipped, so no work is necessary:
-        $test = new ILSTest();
-        if (!$test->continuousIntegrationRunning()) {
-            return;
-        }
-        // Fail if there are already users in the database (we don't want to run this
-        // on a real system -- it's only meant for the continuous integration server)
-        $userTable = $test->getTable('User');
-        if (count($userTable->select()) > 0) {
-            return self::markTestSkipped(
-                'Test cannot run with pre-existing user data!'
-            );
-        }
+        return static::failIfUsersExist();
     }
 
     /**
@@ -200,20 +190,7 @@ class ILSTest extends \VuFindTest\Unit\DbTestCase
      */
     public static function tearDownAfterClass()
     {
-        // If CI is not running, all tests were skipped, so no work is necessary:
-        $test = new ILSTest();
-        if (!$test->continuousIntegrationRunning()) {
-            return;
-        }
-
-        // Delete test user
-        $test = new ILSTest();
-        $userTable = $test->getTable('User');
-        $user = $userTable->getByUsername('testuser', false);
-        if (empty($user)) {
-            throw new \Exception('Problem deleting expected user.');
-        }
-        $user->delete();
+        static::tearDownUsers('testuser');
     }
 
     /**
diff --git a/module/VuFind/tests/integration-tests/src/VuFindTest/Auth/ShibbolethTest.php b/module/VuFind/tests/integration-tests/src/VuFindTest/Auth/ShibbolethTest.php
index a8433274fac2bd5d72d29c3b94a19036a7429a81..0e72749c24d159bd404733872295272d7cd46921 100644
--- a/module/VuFind/tests/integration-tests/src/VuFindTest/Auth/ShibbolethTest.php
+++ b/module/VuFind/tests/integration-tests/src/VuFindTest/Auth/ShibbolethTest.php
@@ -39,26 +39,16 @@ use VuFind\Auth\Shibboleth, VuFind\Db\Table\User, Zend\Config\Config;
  */
 class ShibbolethTest extends \VuFindTest\Unit\DbTestCase
 {
+    use \VuFindTest\Unit\UserCreationTrait;
+
     /**
      * Standard setup method.
      *
-     * @return void
+     * @return mixed
      */
     public static function setUpBeforeClass()
     {
-        // If CI is not running, all tests were skipped, so no work is necessary:
-        $test = new ShibbolethTest();
-        if (!$test->continuousIntegrationRunning()) {
-            return;
-        }
-        // Fail if there are already users in the database (we don't want to run this
-        // on a real system -- it's only meant for the continuous integration server)
-        $userTable = $test->getTable('User');
-        if (count($userTable->select()) > 0) {
-            return self::markTestSkipped(
-                'Test cannot run with pre-existing user data!'
-            );
-        }
+        return static::failIfUsersExist();
     }
 
     /**
@@ -234,18 +224,6 @@ class ShibbolethTest extends \VuFindTest\Unit\DbTestCase
      */
     public static function tearDownAfterClass()
     {
-        // If CI is not running, all tests were skipped, so no work is necessary:
-        $test = new ShibbolethTest();
-        if (!$test->continuousIntegrationRunning()) {
-            return;
-        }
-
-        // Delete test user
-        $userTable = $test->getTable('User');
-        $user = $userTable->getByUsername('testuser', false);
-        if (empty($user)) {
-            throw new \Exception('Problem deleting expected user.');
-        }
-        $user->delete();
-    }
+         static::tearDownUsers('testuser');
+   }
 }
\ No newline at end of file
diff --git a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/FavoritesTest.php b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/FavoritesTest.php
index 46ec58afed524782fdb65597866542827f8a7343..80c1abdbdb3b801b85fd00a7a6f3b92cc4f06e31 100644
--- a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/FavoritesTest.php
+++ b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/FavoritesTest.php
@@ -38,18 +38,16 @@ namespace VuFindTest\Mink;
  */
 class FavoritesTest extends \VuFindTest\Unit\MinkTestCase
 {
-    protected static $hash;
-    protected static $hash2;
+    use \VuFindTest\Unit\UserCreationTrait;
 
     /**
      * Standard setup method.
      *
-     * @return void
+     * @return mixed
      */
     public static function setUpBeforeClass()
     {
-        self::$hash = substr(md5(time() * 2), 0, 16);
-        self::$hash2 = substr(md5(time()), 0, 16);
+        return static::failIfUsersExist();
     }
 
     /**
@@ -104,7 +102,7 @@ class FavoritesTest extends \VuFindTest\Unit\MinkTestCase
         $page->findById('account_firstname')->setValue('Tester');
         $page->findById('account_lastname')->setValue('McTestenson');
         $page->findById('account_email')->setValue('blargasaurus');
-        $page->findById('account_username')->setValue(self::$hash);
+        $page->findById('account_username')->setValue('username1');
         $page->findById('account_password')->setValue('test');
         $page->findById('account_password2')->setValue('test');
         $this->assertNull(
@@ -113,7 +111,7 @@ class FavoritesTest extends \VuFindTest\Unit\MinkTestCase
         $page->find('css', '.modal-body .btn.btn-primary')->click();
         $this->assertNotNull($page->findById('account_firstname'));
         // Correct
-        $page->findById('account_email')->setValue(self::$hash . '@ignore.com');
+        $page->findById('account_email')->setValue('username1@ignore.com');
         $page->find('css', '.modal-body .btn.btn-primary')->click();
         $this->assertNotNull($page->findById('save_list'));
         // Make list
@@ -159,7 +157,7 @@ class FavoritesTest extends \VuFindTest\Unit\MinkTestCase
         $page->find('css', '.modal-body .btn.btn-primary')->click();
         $this->assertNotNull($page->find('css', $username));
         // - wrong
-        $page->find('css', $username)->setValue(self::$hash);
+        $page->find('css', $username)->setValue('username1');
         $page->find('css', $password)->setValue('superwrong');
         $page->find('css', '.modal-body .btn.btn-primary')->click();
         $this->assertNotNull($page->find('css', $username));
@@ -202,7 +200,7 @@ class FavoritesTest extends \VuFindTest\Unit\MinkTestCase
         $page = $this->gotoRecord($session);
         // Login
         $page->find('css', '#loginOptions a')->click();
-        $page->find('css', '.modal-body [name="username"]')->setValue(self::$hash);
+        $page->find('css', '.modal-body [name="username"]')->setValue('username1');
         $page->find('css', '.modal-body [name="password"]')->setValue('test');
         $page->find('css', '.modal-body .btn.btn-primary')->click();
         $session->reload();
@@ -237,7 +235,7 @@ class FavoritesTest extends \VuFindTest\Unit\MinkTestCase
         $page->findById('account_lastname')->setValue('McTestenson');
         $page->findById('account_password')->setValue('test');
         $page->findById('account_password2')->setValue('test');
-        $page->findById('account_username')->setValue(self::$hash2);
+        $page->findById('account_username')->setValue('username2');
         // Invalid email
         $page->findById('account_email')->setValue('blargasaurus');
         $this->assertNull(
@@ -245,12 +243,12 @@ class FavoritesTest extends \VuFindTest\Unit\MinkTestCase
         );
         $page->find('css', '.modal-body .btn.btn-primary')->click();
         $this->assertNotNull($page->findById('account_firstname'));
-        $page->findById('account_email')->setValue(self::$hash2 . '@ignore.com');
+        $page->findById('account_email')->setValue('username2@ignore.com');
         // Test taken
-        $page->findById('account_username')->setValue(self::$hash);
+        $page->findById('account_username')->setValue('username1');
         $page->find('css', '.modal-body .btn.btn-primary')->click();
         $this->assertNotNull($page->findById('account_firstname'));
-        $page->findById('account_username')->setValue(self::$hash2);
+        $page->findById('account_username')->setValue('username2');
         // Correct
         $page->find('css', '.modal-body .btn.btn-primary')->click();
         $this->assertNotNull($page->findById('save_list'));
@@ -301,7 +299,7 @@ class FavoritesTest extends \VuFindTest\Unit\MinkTestCase
         $page->find('css', '.modal-body .btn.btn-primary')->click();
         $this->assertNotNull($page->find('css', $username));
         // - for real
-        $page->find('css', $username)->setValue(self::$hash2);
+        $page->find('css', $username)->setValue('username2');
         $page->find('css', $password)->setValue('test');
         $page->find('css', '.modal-body .btn.btn-primary')->click();
         // Make sure we don't have Favorites because we have another populated list
@@ -340,7 +338,7 @@ class FavoritesTest extends \VuFindTest\Unit\MinkTestCase
         $page = $this->gotoSearch($session);
         // Login
         $page->find('css', '#loginOptions a')->click();
-        $page->find('css', '.modal-body [name="username"]')->setValue(self::$hash2);
+        $page->find('css', '.modal-body [name="username"]')->setValue('username2');
         $page->find('css', '.modal-body [name="password"]')->setValue('test');
         $page->find('css', '.modal-body .btn.btn-primary')->click();
         $session->reload();
@@ -371,20 +369,6 @@ class FavoritesTest extends \VuFindTest\Unit\MinkTestCase
      */
     public static function tearDownAfterClass()
     {
-        // If CI is not running, all tests were skipped, so no work is necessary:
-        $test = new static();   // create instance of current class
-        if (!$test->continuousIntegrationRunning()) {
-            return;
-        }
-
-        // Delete test user
-        $userTable = $test->getTable('User');
-        foreach ([self::$hash, self::$hash2] as $username) {
-            $user = $userTable->getByUsername($username, false);
-            if (empty($user)) {
-                throw new \Exception('Problem deleting expected user.');
-            }
-            $user->delete();
-        }
+        static::tearDownUsers(['username1', 'username2']);
     }
 }