diff --git a/module/VuFind/src/VuFind/Reserves/CsvReader.php b/module/VuFind/src/VuFind/Reserves/CsvReader.php
index f5b3cce6b4965f86f493b245d0c7f7a85d7b8c9b..ebc13329bd2e737c02e029ed7e09f5c3c5e6fa7d 100644
--- a/module/VuFind/src/VuFind/Reserves/CsvReader.php
+++ b/module/VuFind/src/VuFind/Reserves/CsvReader.php
@@ -1,6 +1,6 @@
 <?php
 /**
- * CLI Controller Module
+ * Support class to build reserves data from CSV file(s).
  *
  * PHP version 5
  *
@@ -27,7 +27,7 @@
  */
 namespace VuFind\Reserves;
 
- /**
+/**
  * Support class to build reserves data from CSV file(s).
  *
  * @category VuFind2
@@ -94,6 +94,13 @@ class CsvReader
      */
     protected $loaded = false;
 
+    /**
+     * Error messages collected during loading.
+     *
+     * @var string
+     */
+    protected $errors = '';
+
     /**
      * Constructor
      *
@@ -150,12 +157,11 @@ class CsvReader
             throw new \Exception("Could not open $fn!");
         }
         $lineNo = $goodLines = 0;
-        $errors = '';
         while ($line = fgetcsv($fh, 0, $this->delimiter)) {
             $lineNo++;
 
             if (count($line) < count($this->template)) {
-                $errors .= "Skipping incomplete row: $fn, line $lineNo\n";
+                $this->errors .= "Skipping incomplete row: $fn, line $lineNo\n";
                 continue;
             }
 
@@ -176,7 +182,7 @@ class CsvReader
 
             $bibId = trim($line[$this->template['BIB_ID']]);
             if ($bibId == '') {
-                $errors .= "Skipping empty/missing Bib ID: $fn, line $lineNo\n";
+                $this->errors .= "Skipping empty/missing Bib ID: $fn, line $lineNo\n";
                 continue;
             }
 
@@ -191,7 +197,7 @@ class CsvReader
         fclose($fh);
         if ($goodLines == 0) {
             throw new \Exception(
-                "Could not find valid data. Details:\n" . trim($errors)
+                "Could not find valid data. Details:\n" . trim($this->errors)
             );
         }
     }
@@ -261,4 +267,14 @@ class CsvReader
         $this->load();
         return $this->reserves;
     }
+
+    /**
+     * Get collected error messages
+     *
+     * @return string
+     */
+    public function getErrors()
+    {
+        return $this->errors;
+    }
 }
diff --git a/module/VuFind/tests/fixtures/reserves/empty.csv b/module/VuFind/tests/fixtures/reserves/empty.csv
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/module/VuFind/tests/fixtures/reserves/reserves.csv b/module/VuFind/tests/fixtures/reserves/reserves.csv
new file mode 100644
index 0000000000000000000000000000000000000000..6ed9651c5b90b422e81a0cbc7280b5467f92b8d1
--- /dev/null
+++ b/module/VuFind/tests/fixtures/reserves/reserves.csv
@@ -0,0 +1,5 @@
+1,English 101,Mr. English,English
+2,Math 101,Ms. Math,Math
+,Bad Row,Junk,Garbage
+3,Geography 101,M. Geography,Geography
+4,partial
\ No newline at end of file
diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Reserves/CsvReaderTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Reserves/CsvReaderTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f8abb31f9cd7beb1c2952b46acf9edb4b43b5b52
--- /dev/null
+++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Reserves/CsvReaderTest.php
@@ -0,0 +1,169 @@
+<?php
+/**
+ * Course Reserves CSV Loader Test Class
+ *
+ * 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\Reserves;
+use VuFind\Reserves\CsvReader;
+
+/**
+ * Course Reserves CSV Loader Test Class
+ *
+ * @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
+ */
+class CsvReaderTest extends \VuFindTest\Unit\TestCase
+{
+    /**
+     * Test getInstructors()
+     *
+     * @return void
+     */
+    public function testGetInstructors()
+    {
+        $instructors = array(
+            'Mr. English' => 'Mr. English',
+            'Ms. Math' => 'Ms. Math',
+            'Junk' => 'Junk',
+            'M. Geography' => 'M. Geography',
+        );
+        $this->assertEquals($instructors, $this->getReader()->getInstructors());
+    }
+
+    /**
+     * Test getCourses()
+     *
+     * @return void
+     */
+    public function testGetCourses()
+    {
+        $courses = array(
+            'English 101' => 'English 101',
+            'Math 101' => 'Math 101',
+            'Bad Row' => 'Bad Row',
+            'Geography 101' => 'Geography 101',
+        );
+        $this->assertEquals($courses, $this->getReader()->getCourses());
+    }
+
+    /**
+     * Test getDepartments()
+     *
+     * @return void
+     */
+    public function testGetDepartments()
+    {
+        $departments = array(
+            'English' => 'English',
+            'Math' => 'Math',
+            'Garbage' => 'Garbage',
+            'Geography' => 'Geography',
+        );
+        $this->assertEquals($departments, $this->getReader()->getDepartments());
+    }
+
+    /**
+     * Test getReserves()
+     *
+     * @return void
+     */
+    public function testGetReserves()
+    {
+        $reserves = array(
+            array(
+                'BIB_ID' => 1,
+                'INSTRUCTOR_ID' => 'Mr. English',
+                'COURSE_ID' => 'English 101',
+                'DEPARTMENT_ID' => 'English',
+            ),
+            array(
+                'BIB_ID' => 2,
+                'INSTRUCTOR_ID' => 'Ms. Math',
+                'COURSE_ID' => 'Math 101',
+                'DEPARTMENT_ID' => 'Math',
+            ),
+            array(
+                'BIB_ID' => 3,
+                'INSTRUCTOR_ID' => 'M. Geography',
+                'COURSE_ID' => 'Geography 101',
+                'DEPARTMENT_ID' => 'Geography',
+            ),
+        );
+        $this->assertEquals($reserves, $this->getReader()->getReserves());
+    }
+
+    /**
+     * Test getErrors()
+     *
+     * @return void
+     */
+    public function testGetErrors()
+    {
+        $reader = $this->getReader();
+        $reader->getReserves();
+        $fixture = $this->getFixturePath('reserves.csv');
+        $errors = "Skipping empty/missing Bib ID: $fixture, line 3\nSkipping incomplete row: $fixture, line 5\n";
+        $this->assertEquals($errors, $reader->getErrors());
+    }
+
+    /**
+     * Test loading an empty file.
+     *
+     * @return void
+     * @expectedException \Exception
+     * @expectedExceptionMessage Could not find valid data. Details:
+     */
+    public function testEmptyFile()
+    {
+        $this->getReader('empty.csv')->getReserves();
+    }
+
+    /**
+     * Get a reader object with the fixture loaded.
+     *
+     * @param string $fixture Name of file to load
+     *
+     * @return CsvReader
+     */
+    protected function getReader($fixture = 'reserves.csv')
+    {
+        return new CsvReader($this->getFixturePath($fixture));
+    }
+
+    /**
+     * Get a fixture path
+     *
+     * @param string $fixture Name of file to load
+     *
+     * @return string
+     */
+    protected function getFixturePath($fixture)
+    {
+        return realpath(__DIR__ . '/../../../../fixtures/reserves/' . $fixture);
+    }
+}
\ No newline at end of file