diff --git a/module/VuFind/src/VuFindTest/Search/TestHarness/Options.php b/module/VuFind/src/VuFindTest/Search/TestHarness/Options.php
new file mode 100644
index 0000000000000000000000000000000000000000..3f3b93a85efc4b3bfd62b3631fc168bd25a243f2
--- /dev/null
+++ b/module/VuFind/src/VuFindTest/Search/TestHarness/Options.php
@@ -0,0 +1,52 @@
+<?php
+/**
+ * Test options search model.
+ *
+ * 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  Search_Base
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+namespace VuFindTest\Search\TestHarness;
+
+/**
+ * Test options search model.
+ *
+ * This abstract class defines the results methods for modeling a search in VuFind.
+ *
+ * @category VuFind2
+ * @package  Search_Base
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+class Options extends \VuFind\Search\Base\Options
+{
+    /**
+     * Return the route name for the search results action.
+     *
+     * @return string
+     */
+    public function getSearchAction()
+    {
+        return 'fake-fake';
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFindTest/Search/TestHarness/Params.php b/module/VuFind/src/VuFindTest/Search/TestHarness/Params.php
new file mode 100644
index 0000000000000000000000000000000000000000..d27f9d2668b91d11fd450045f2659aa2cf21aba1
--- /dev/null
+++ b/module/VuFind/src/VuFindTest/Search/TestHarness/Params.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * Test params search model.
+ *
+ * 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  Search_Base
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+namespace VuFindTest\Search\TestHarness;
+
+/**
+ * Test params search model.
+ *
+ * This abstract class defines the results methods for modeling a search in VuFind.
+ *
+ * @category VuFind2
+ * @package  Search_Base
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+class Params extends \VuFind\Search\Base\Params
+{
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFindTest/Search/TestHarness/Results.php b/module/VuFind/src/VuFindTest/Search/TestHarness/Results.php
new file mode 100644
index 0000000000000000000000000000000000000000..475a293e420841bf282f9a0e74dba921e9c26378
--- /dev/null
+++ b/module/VuFind/src/VuFindTest/Search/TestHarness/Results.php
@@ -0,0 +1,122 @@
+<?php
+/**
+ * Test results search model.
+ *
+ * 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  Search_Base
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+namespace VuFindTest\Search\TestHarness;
+use VuFindTest\RecordDriver\TestHarness as RecordDriver;
+
+/**
+ * Test results search model.
+ *
+ * This abstract class defines the results methods for modeling a search in VuFind.
+ *
+ * @category VuFind2
+ * @package  Search_Base
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+class Results extends \VuFind\Search\Base\Results
+{
+    /**
+     * Fake expected total
+     *
+     * @var int
+     */
+    protected $fakeExpectedTotal;
+
+    /**
+     * Cache for fake drivers
+     *
+     * @var array
+     */
+    protected $driverCache = array();
+
+    /**
+     * Constructor
+     *
+     * @param \VuFind\Search\Base\Params $params Object representing user search
+     * parameters.
+     * @param int                        $total  Total result set size to simulate
+     */
+    public function __construct(Params $params, $total = 100)
+    {
+        parent::__construct($params);
+        $this->fakeExpectedTotal = $total;
+        $this->searchId = 'fake';   // fill a fake value here so we don't hit the DB
+    }
+
+    /**
+     * Returns the stored list of facets for the last search
+     *
+     * @param array $filter Array of field => on-screen description listing
+     * all of the desired facet fields; set to null to get all configured values.
+     *
+     * @return array        Facets data arrays
+     */
+    public function getFacetList($filter = null)
+    {
+        // not supported
+        return array();
+    }
+
+    /**
+     * Abstract support method for performAndProcessSearch -- perform a search based
+     * on the parameters passed to the object.  This method is responsible for
+     * filling in all of the key class properties: results, resultTotal, etc.
+     *
+     * @return void
+     */
+    protected function performSearch()
+    {
+        $this->resultTotal = $this->fakeExpectedTotal;
+        $this->results = array();
+        $limit  = $this->getParams()->getLimit();
+        $start = $this->getStartRecord();
+        for ($i = $start; $i < $start + $limit; $i++) {
+            if ($i > $this->resultTotal) {
+                break;
+            }
+            $this->results[] = $this->getMockRecordDriver($i);
+        }
+    }
+
+    /**
+     * Get a fake record driver
+     *
+     * @var string $id ID to use
+     *
+     * @return RecordDriver
+     */
+    public function getMockRecordDriver($id)
+    {
+        if (!isset($this->driverCache[$id])) {
+            $this->driverCache[$id] = new RecordDriver();
+            $this->driverCache[$id]->setRawData(array('UniqueID' => $id));
+        }
+        return $this->driverCache[$id];
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Controller/Plugin/ResultScrollerTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Controller/Plugin/ResultScrollerTest.php
index 5168ef802aff4e445d4caad1ec6e6e511a05b511..87ab95abb739f6a9a333463faeadfeaf91afbe31 100644
--- a/module/VuFind/tests/unit-tests/src/VuFindTest/Controller/Plugin/ResultScrollerTest.php
+++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Controller/Plugin/ResultScrollerTest.php
@@ -42,13 +42,6 @@ use VuFindTest\Unit\TestCase as TestCase;
  */
 class ResultScrollerTest extends TestCase
 {
-    /**
-     * Cache for record driver mocks.
-     *
-     * @var array
-     */
-    protected $recordDrivers = array();
-
     /**
      * Test disabled behavior
      *
@@ -57,12 +50,13 @@ class ResultScrollerTest extends TestCase
     public function testDisabled()
     {
         $plugin = new ResultScroller(false);
-        $this->assertFalse($plugin->init($this->getMockResults()));
+        $results = $this->getMockResults();
+        $this->assertFalse($plugin->init($results));
         $expected = array(
             'previousRecord'=>null, 'nextRecord'=>null,
             'currentPosition'=>null, 'resultTotal'=>null
         );
-        $this->assertEquals($expected, $plugin->getScrollData($this->getMockRecordDriver('foo')));
+        $this->assertEquals($expected, $plugin->getScrollData($results->getMockRecordDriver(1)));
     }
 
     /**
@@ -73,14 +67,13 @@ class ResultScrollerTest extends TestCase
     public function testScrollingInMiddleOfPage()
     {
         $results = $this->getMockResults(1, 10, 10);
-        $plugin = $this->getMockResultScroller();
-        $plugin->expects($this->any())->method('restoreLastSearch')->will($this->returnValue($results));
+        $plugin = $this->getMockResultScroller($results);
         $this->assertTrue($plugin->init($results));
         $expected = array(
             'previousRecord'=>'VuFind|4', 'nextRecord'=>'VuFind|6',
             'currentPosition'=>5, 'resultTotal'=>10
         );
-        $this->assertEquals($expected, $plugin->getScrollData($this->getMockRecordDriver(5)));
+        $this->assertEquals($expected, $plugin->getScrollData($results->getMockRecordDriver(5)));
     }
 
     /**
@@ -91,14 +84,13 @@ class ResultScrollerTest extends TestCase
     public function testScrollingAtStartOfFirstPage()
     {
         $results = $this->getMockResults(1, 10, 10);
-        $plugin = $this->getMockResultScroller();
-        $plugin->expects($this->any())->method('restoreLastSearch')->will($this->returnValue($results));
+        $plugin = $this->getMockResultScroller($results);
         $this->assertTrue($plugin->init($results));
         $expected = array(
             'previousRecord'=>null, 'nextRecord'=>'VuFind|2',
             'currentPosition'=>1, 'resultTotal'=>10
         );
-        $this->assertEquals($expected, $plugin->getScrollData($this->getMockRecordDriver(1)));
+        $this->assertEquals($expected, $plugin->getScrollData($results->getMockRecordDriver(1)));
     }
 
     /**
@@ -109,14 +101,13 @@ class ResultScrollerTest extends TestCase
     public function testScrollingAtEndOfLastPage()
     {
         $results = $this->getMockResults(1, 10, 10);
-        $plugin = $this->getMockResultScroller();
-        $plugin->expects($this->any())->method('restoreLastSearch')->will($this->returnValue($results));
+        $plugin = $this->getMockResultScroller($results);
         $this->assertTrue($plugin->init($results));
         $expected = array(
             'previousRecord'=>'VuFind|9', 'nextRecord'=>null,
             'currentPosition'=>10, 'resultTotal'=>10
         );
-        $this->assertEquals($expected, $plugin->getScrollData($this->getMockRecordDriver(10)));
+        $this->assertEquals($expected, $plugin->getScrollData($results->getMockRecordDriver(10)));
     }
 
     /**
@@ -127,107 +118,83 @@ class ResultScrollerTest extends TestCase
     public function testScrollingAtEndOfLastPageInMultiPageScenario()
     {
         $results = $this->getMockResults(2, 10, 17);
-        $plugin = $this->getMockResultScroller();
-        $plugin->expects($this->any())->method('restoreLastSearch')->will($this->returnValue($results));
+        $plugin = $this->getMockResultScroller($results);
         $this->assertTrue($plugin->init($results));
         $expected = array(
             'previousRecord'=>'VuFind|16', 'nextRecord'=>null,
             'currentPosition'=>17, 'resultTotal'=>17
         );
-        $this->assertEquals($expected, $plugin->getScrollData($this->getMockRecordDriver(17)));
+        $this->assertEquals($expected, $plugin->getScrollData($results->getMockRecordDriver(17)));
     }
 
     /**
-     * Get mock record driver
-     *
-     * @param string $id     ID
-     * @param string $source Backend
+     * Test scrolling at beginning of middle page.
      *
-     * @return \VuFind\RecordDriver\AbstractBase
+     * @return void
      */
-    protected function getMockRecordDriver($id, $source = 'VuFind')
+    public function testScrollingAtStartOfMiddlePage()
     {
-        if (!isset($this->recordDrivers["$source|$id"])) {
-            $driver = $this->getMockBuilder('VuFind\RecordDriver\AbstractBase')
-                ->disableOriginalConstructor()
-                ->getMock();
-            $driver->expects($this->any())->method('getUniqueId')->will($this->returnValue($id));
-            $driver->expects($this->any())->method('getResourceSource')->will($this->returnValue($source));
-            $this->recordDrivers["$source|$id"] = $driver;
-        }
-        return $this->recordDrivers["$source|$id"];
+        $results = $this->getMockResults(2, 10, 30);
+        $plugin = $this->getMockResultScroller($results);
+        $this->assertTrue($plugin->init($results));
+        $expected = array(
+            'previousRecord'=>'VuFind|10', 'nextRecord'=>'VuFind|12',
+            'currentPosition'=>11, 'resultTotal'=>30
+        );
+        $this->assertEquals($expected, $plugin->getScrollData($results->getMockRecordDriver(11)));
     }
 
     /**
-     * Get mock search results
+     * Test scrolling at end of middle page.
      *
-     * @param int $page  Current page number
-     * @param int $limit Page size
-     * @param int $total Total size of fake result set
-     *
-     * @return \VuFind\Search\Base\Results
+     * @return void
      */
-    protected function getMockResults($page = 0, $limit = 0, $total = 0)
+    public function testScrollingAtEndOfMiddlePage()
     {
-        $results = $this->getMockBuilder('VuFind\Search\Solr\Results')
-            ->disableOriginalConstructor()
-            ->getMock();
-        $results->expects($this->any())->method('getSearchId')->will($this->returnValue('dummy-search-id'));
-        $results->expects($this->any())->method('getParams')->will($this->returnValue($this->getMockParams($page, $limit)));
-        $results->expects($this->any())->method('getResultTotal')->will($this->returnValue($total));
-        $results->expects($this->any())->method('getResults')->will($this->returnValue($this->getResultSet($page, $limit, $total)));
-        return $results;
+        $results = $this->getMockResults(2, 10, 30);
+        $plugin = $this->getMockResultScroller($results);
+        $this->assertTrue($plugin->init($results));
+        $expected = array(
+            'previousRecord'=>'VuFind|19', 'nextRecord'=>'VuFind|21',
+            'currentPosition'=>20, 'resultTotal'=>30
+        );
+        $this->assertEquals($expected, $plugin->getScrollData($results->getMockRecordDriver(20)));
     }
 
     /**
-     * Get mock search params
+     * Get mock search results
      *
      * @param int $page  Current page number
      * @param int $limit Page size
+     * @param int $total Total size of fake result set
      *
-     * @return \VuFind\Search\Base\Params
+     * @return \VuFind\Search\Base\Results
      */
-    protected function getMockParams($page = 0, $limit = 0)
+    protected function getMockResults($page = 1, $limit = 20, $total = 0)
     {
-        $params = $this->getMockBuilder('VuFind\Search\Solr\Params')
-            ->disableOriginalConstructor()
-            ->getMock();
-        $params->expects($this->any())->method('getPage')->will($this->returnValue($page));
-        $params->expects($this->any())->method('getLimit')->will($this->returnValue($limit));
-        return $params;
+        $pm = $this->getMockBuilder('VuFind\Config\PluginManager')->disableOriginalConstructor()->getMock();
+        $options = new \VuFindTest\Search\TestHarness\Options($pm);
+        $params = new \VuFindTest\Search\TestHarness\Params($options, $pm);
+        $params->setPage($page);
+        $params->setLimit($limit);
+        $results = new \VuFindTest\Search\TestHarness\Results($params, $total);
+        return $results;
     }
 
     /**
      * Get mock result scroller
      *
-     * @param array $methods Methods to mock
+     * @param \VuFind\Search\Base\Results restoreLastSearch results (null to ignore)
+     * @param array                       $methods Methods to mock
      *
      * @return ResultScroller
      */
-    protected function getMockResultScroller($methods = array('restoreLastSearch', 'rememberSearch'))
-    {
-        return $this->getMock('VuFind\Controller\Plugin\ResultScroller', $methods);
-    }
-
-    /**
-     * Get set of fake record drivers.
-     *
-     * @param int $page  Current page number
-     * @param int $limit Page size
-     * @param int $total Total size of fake result set
-     *
-     * @return array
-     */
-    protected function getResultSet($page, $limit, $total)
+    protected function getMockResultScroller($results = null, $methods = array('restoreLastSearch', 'rememberSearch'))
     {
-        $retVal = array();
-        for ($i = 1; $i <= $limit; $i++) {
-            $current = ($page - 1) * $limit + $i;
-            if ($current > $total) {
-                break;
-            }
-            $retVal[] = $this->getMockRecordDriver($current);
+        $mock = $this->getMock('VuFind\Controller\Plugin\ResultScroller', $methods);
+        if (in_array('restoreLastSearch', $methods) && null !== $results) {
+            $mock->expects($this->any())->method('restoreLastSearch')->will($this->returnValue($results));
         }
-        return $retVal;
+        return $mock;
     }
 }
\ No newline at end of file