diff --git a/module/VuFind/src/VuFind/Controller/QRCodeController.php b/module/VuFind/src/VuFind/Controller/QRCodeController.php
index 1064045555d8b68a7e219a6c7751a31e31256a7c..0967300e7e32e59b9c171dd50db79dc54b830859 100644
--- a/module/VuFind/src/VuFind/Controller/QRCodeController.php
+++ b/module/VuFind/src/VuFind/Controller/QRCodeController.php
@@ -113,7 +113,7 @@ class QRCodeController extends AbstractBase
         $headers->addHeaderLine(
             'Content-type', $this->getLoader()->getContentType()
         );
-        $response->setContent($this->getLoader()->getQrCode());
+        $response->setContent($this->getLoader()->getImage());
         return $response;
     }
 }
diff --git a/module/VuFind/src/VuFind/Cover/Loader.php b/module/VuFind/src/VuFind/Cover/Loader.php
index c9b3bbc0768aa2a1e9646b76d1689bcfa5b0abea..05c480c3e9a4793afa954ce15917e5fe534d6b4a 100644
--- a/module/VuFind/src/VuFind/Cover/Loader.php
+++ b/module/VuFind/src/VuFind/Cover/Loader.php
@@ -28,8 +28,7 @@
  */
 namespace VuFind\Cover;
 use VuFind\Code\ISBN,
-    VuFind\Content\Covers\PluginManager as ApiManager,
-    Zend\Log\LoggerInterface;
+    VuFind\Content\Covers\PluginManager as ApiManager;
 
 /**
  * Book Cover Generator
@@ -41,7 +40,7 @@ use VuFind\Code\ISBN,
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     http://vufind.org/wiki/use_of_external_content Wiki
  */
-class Loader implements \Zend\Log\LoggerAwareInterface
+class Loader extends \VuFind\ImageLoader
 {
     /**
      * filename constructed from ISBN
@@ -127,34 +126,6 @@ class Loader implements \Zend\Log\LoggerAwareInterface
      */
     protected $type;
 
-    /**
-     * Property for storing raw image data; may be null if image is unavailable
-     *
-     * @var string
-     */
-    protected $image = null;
-
-    /**
-     * Content type of data in $image property
-     *
-     * @var string
-     */
-    protected $contentType = null;
-
-    /**
-     * Logger (or false for none)
-     *
-     * @var LoggerInterface|bool
-     */
-    protected $logger = false;
-
-    /**
-     * Theme tools
-     *
-     * @var \VuFindTheme\ThemeInfo
-     */
-    protected $themeTools;
-
     /**
      * Constructor
      *
@@ -168,69 +139,17 @@ class Loader implements \Zend\Log\LoggerAwareInterface
     public function __construct($config, ApiManager $manager,
         \VuFindTheme\ThemeInfo $theme, \Zend\Http\Client $client, $baseDir = null
     ) {
+        $this->setThemeInfo($theme);
         $this->config = $config;
+        $this->configuredFailImage = isset($config->Content->noCoverAvailableImage)
+            ? $config->Content->noCoverAvailableImage : null;
         $this->apiManager = $manager;
-        $this->themeTools = $theme;
         $this->client = $client;
         $this->baseDir = (null === $baseDir)
             ? rtrim(sys_get_temp_dir(), '\\/') . '/covers'
             : rtrim($baseDir, '\\/');
     }
 
-    /**
-     * Set the logger
-     *
-     * @param LoggerInterface $logger Logger to use.
-     *
-     * @return void
-     */
-    public function setLogger(LoggerInterface $logger)
-    {
-        $this->logger = $logger;
-    }
-
-    /**
-     * Log a debug message.
-     *
-     * @param string $msg Message to log.
-     *
-     * @return void
-     */
-    protected function debug($msg)
-    {
-        if ($this->logger) {
-            $this->logger->debug($msg);
-        }
-    }
-
-    /**
-     * Get the image data (usually called after loadImage)
-     *
-     * @return string
-     */
-    public function getImage()
-    {
-        // No image loaded?  Use "unavailable" as default:
-        if (null === $this->image) {
-            $this->loadUnavailable();
-        }
-        return $this->image;
-    }
-
-    /**
-     * Get the content type of the current image (usually called after loadImage)
-     *
-     * @return string
-     */
-    public function getContentType()
-    {
-        // No content type loaded?  Use "unavailable" as default:
-        if (null === $this->contentType) {
-            $this->loadUnavailable();
-        }
-        return $this->contentType;
-    }
-
     /**
      * Get Cover Generator Object
      *
@@ -449,93 +368,6 @@ class Loader implements \Zend\Log\LoggerAwareInterface
         return false;
     }
 
-    /**
-     * Find a file in the themes (return false if no file exists).
-     *
-     * @param string $path    Relative path of file to find.
-     * @param array  $formats Optional array of suffixes to add to $path while
-     * searching theme (used to check multiple extensions in each theme).
-     *
-     * @return string|bool
-     */
-    protected function searchTheme($path, $formats = array(''))
-    {
-        // Check all supported image formats:
-        $filenames = array();
-        foreach ($formats as $format) {
-            $filenames[] =  $path . $format;
-        }
-        $fileMatch = $this->themeTools->findContainingTheme($filenames, true);
-        return empty($fileMatch) ? false : $fileMatch;
-    }
-
-    /**
-     * Load the user-specified "cover unavailable" graphic (or default if none
-     * specified).
-     *
-     * @return void
-     * @author Thomas Schwaerzler <vufind-tech@lists.sourceforge.net>
-     */
-    public function loadUnavailable()
-    {
-        // No setting -- use default, and don't log anything:
-        if (!isset($this->config->Content->noCoverAvailableImage)) {
-            return $this->loadDefaultFailImage();
-        }
-
-        // Setting found -- get "no cover" image from config.ini:
-        $configuredImage = $this->config->Content->noCoverAvailableImage;
-        $noCoverImage = $this->searchTheme($configuredImage);
-
-        // If file is blank/inaccessible, log error and display default:
-        if (empty($noCoverImage) || !file_exists($noCoverImage)
-            || !is_readable($noCoverImage)
-        ) {
-            $this->debug("Cannot access '{$configuredImage}'");
-            return $this->loadDefaultFailImage();
-        }
-
-        // Array containing map of allowed file extensions to mimetypes
-        // (to be extended)
-        $allowedFileExtensions = array(
-            "gif" => "image/gif",
-            "jpeg" => "image/jpeg", "jpg" => "image/jpeg",
-            "png" => "image/png",
-            "tiff" => "image/tiff", "tif" => "image/tiff"
-        );
-
-        // Log error and bail out if file lacks a known image extension:
-        $parts = explode('.', $noCoverImage);
-        $fileExtension = strtolower(end($parts));
-        if (!array_key_exists($fileExtension, $allowedFileExtensions)) {
-            $this->debug(
-                "Illegal file-extension '$fileExtension' for image '$noCoverImage'"
-            );
-            return $this->loadDefaultFailImage();
-        }
-
-        // Get mime type from file extension:
-        $this->contentType = $allowedFileExtensions[$fileExtension];
-
-        // Load the image data:
-        $this->image = file_get_contents($noCoverImage);
-    }
-
-    /**
-     * Display the default "cover unavailable" graphic.
-     *
-     * @return void
-     */
-    protected function loadDefaultFailImage()
-    {
-        $this->contentType = 'image/gif';
-        $file = $this->searchTheme('images/noCover2.gif');
-        if (!file_exists($file)) {
-            throw new \Exception('Could not load default fail image.');
-        }
-        $this->image = file_get_contents($file);
-    }
-
     /**
      * Support method for validateAndMoveTempFile -- convert non-JPEG image data to a
      * JPEG file.
diff --git a/module/VuFind/src/VuFind/ImageLoader.php b/module/VuFind/src/VuFind/ImageLoader.php
new file mode 100644
index 0000000000000000000000000000000000000000..46cfeb39e5d341d2b1e39d313fce6be8b5b53fc0
--- /dev/null
+++ b/module/VuFind/src/VuFind/ImageLoader.php
@@ -0,0 +1,264 @@
+<?php
+/**
+ * Base class for loading images (shared by Cover\Loader and QRCode\Loader)
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2007.
+ *
+ * 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  Cover_Generator
+ * @author   Andrew S. Nagy <vufind-tech@lists.sourceforge.net>
+ * @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/use_of_external_content Wiki
+ */
+namespace VuFind;
+use Zend\Log\LoggerInterface;
+
+/**
+ * Base class for loading images (shared by Cover\Loader and QRCode\Loader)
+ *
+ * @category VuFind2
+ * @package  Cover_Generator
+ * @author   Andrew S. Nagy <vufind-tech@lists.sourceforge.net>
+ * @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/use_of_external_content Wiki
+ */
+class ImageLoader implements \Zend\Log\LoggerAwareInterface
+{
+    /**
+     * Property for storing raw image data; may be null if image is unavailable
+     *
+     * @var string
+     */
+    protected $image = null;
+
+    /**
+     * Content type of data in $image property
+     *
+     * @var string
+     */
+    protected $contentType = null;
+
+    /**
+     * Logger (or false for none)
+     *
+     * @var LoggerInterface|bool
+     */
+    protected $logger = false;
+
+    /**
+     * Theme tools
+     *
+     * @var \VuFindTheme\ThemeInfo
+     */
+    protected $themeTools = null;
+
+    /**
+     * User-configured image to load from theme on error.
+     *
+     * @var string
+     */
+    protected $configuredFailImage = null;
+
+    /**
+     * Default image to load from theme if user-configured option fails.
+     *
+     * @var string
+     */
+    protected $defaultFailImage = 'images/noCover2.gif';
+
+    /**
+     * Array containing map of allowed file extensions to mimetypes
+     * (to be extended)
+     *
+     * @var array
+     */
+    protected $allowedFileExtensions = array(
+        "gif" => "image/gif",
+        "jpeg" => "image/jpeg", "jpg" => "image/jpeg",
+        "png" => "image/png",
+        "tiff" => "image/tiff", "tif" => "image/tiff"
+    );
+
+    /**
+     * Setter for dependency
+     *
+     * @param \VuFindTheme\ThemeInfo $theme VuFind theme tools
+     *
+     * @return void
+     */
+    public function setThemeInfo(\VuFindTheme\ThemeInfo $theme)
+    {
+        $this->themeTools = $theme;
+    }
+
+    /**
+     * Set the logger
+     *
+     * @param LoggerInterface $logger Logger to use.
+     *
+     * @return void
+     */
+    public function setLogger(LoggerInterface $logger)
+    {
+        $this->logger = $logger;
+    }
+
+    /**
+     * Log a debug message.
+     *
+     * @param string $msg Message to log.
+     *
+     * @return void
+     */
+    protected function debug($msg)
+    {
+        if ($this->logger) {
+            $this->logger->debug($msg);
+        }
+    }
+
+    /**
+     * Get the image data (not meant to be called until after image is populated)
+     *
+     * @return string
+     */
+    public function getImage()
+    {
+        // No image loaded?  Use "unavailable" as default:
+        if (null === $this->image) {
+            $this->loadUnavailable();
+        }
+        return $this->image;
+    }
+
+    /**
+     * Get the content type of the current image (not meant to be called until after
+     * contentType is populated)
+     *
+     * @return string
+     */
+    public function getContentType()
+    {
+        // No content type loaded?  Use "unavailable" as default:
+        if (null === $this->contentType) {
+            $this->loadUnavailable();
+        }
+        return $this->contentType;
+    }
+
+    /**
+     * Find a file in the themes (return false if no file exists).
+     *
+     * @param string $path    Relative path of file to find.
+     * @param array  $formats Optional array of suffixes to add to $path while
+     * searching theme (used to check multiple extensions in each theme).
+     *
+     * @return string|bool
+     */
+    protected function searchTheme($path, $formats = array(''))
+    {
+        // Check all supported image formats:
+        $filenames = array();
+        foreach ($formats as $format) {
+            $filenames[] =  $path . $format;
+        }
+        if (null === $this->themeTools) {
+            throw new \Exception('\VuFindTheme\ThemeInfo object missing');
+        }
+        $fileMatch = $this->themeTools->findContainingTheme($filenames, true);
+        return empty($fileMatch) ? false : $fileMatch;
+    }
+
+    /**
+     * Load the user-specified "cover unavailable" graphic (or default if none
+     * specified).
+     *
+     * @return void
+     * @author Thomas Schwaerzler <vufind-tech@lists.sourceforge.net>
+     */
+    public function loadUnavailable()
+    {
+        // No setting -- use default, and don't log anything:
+        if (empty($this->configuredFailImage)) {
+            return $this->loadDefaultFailImage();
+        }
+
+        // Setting found -- get "no cover" image from config.ini:
+        $noCoverImage = $this->searchTheme($this->configuredFailImage);
+
+        // If file is blank/inaccessible, log error and display default:
+        if (empty($noCoverImage) || !file_exists($noCoverImage)
+            || !is_readable($noCoverImage)
+        ) {
+            $this->debug("Cannot access '{$this->configuredFailImage}'");
+            return $this->loadDefaultFailImage();
+        }
+
+        try {
+            // Get mime type from file extension:
+            $this->contentType = $this->getContentTypeFromExtension($noCoverImage);
+        } catch (\Exception $e) {
+            // Log error and bail out if file lacks a known image extension:
+            $this->debug($e->getMessage());
+            return $this->loadDefaultFailImage();
+        }
+
+        // Load the image data:
+        $this->image = file_get_contents($noCoverImage);
+    }
+
+    /**
+     * Display the default "cover unavailable" graphic.
+     *
+     * @return void
+     */
+    protected function loadDefaultFailImage()
+    {
+        $file = $this->searchTheme($this->defaultFailImage);
+        if (!file_exists($file)) {
+            throw new \Exception('Could not load default fail image.');
+        }
+        $this->contentType = $this->getContentTypeFromExtension($file);
+        $this->image = file_get_contents($file);
+    }
+
+    /**
+     * Get the content-type for a file based on extension. Throw an exception if
+     * an illegal extension is provided.
+     *
+     * @param string $filename Filename to analyze.
+     *
+     * @return string
+     * @throws \Exception
+     */
+    protected function getContentTypeFromExtension($filename)
+    {
+        $parts = explode('.', $filename);
+        $fileExtension = strtolower(end($parts));
+        if (!array_key_exists($fileExtension, $this->allowedFileExtensions)) {
+            throw new \Exception(
+                "Illegal file-extension '$fileExtension' for image '$filename'"
+            );
+        }
+
+        // Get mime type from file extension:
+        return $this->allowedFileExtensions[$fileExtension];
+    }
+}
diff --git a/module/VuFind/src/VuFind/QRCode/Loader.php b/module/VuFind/src/VuFind/QRCode/Loader.php
index 2bf41a2c275bc110d6b907cf8e6efe00a2c7e592..a9b91287334cc88e9452dfc1011b1c489bf77dd9 100644
--- a/module/VuFind/src/VuFind/QRCode/Loader.php
+++ b/module/VuFind/src/VuFind/QRCode/Loader.php
@@ -20,7 +20,7 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  *
  * @category VuFind2
- * @package  Cover_Generator
+ * @package  QRCode_Generator
  * @author   Andrew S. Nagy <vufind-tech@lists.sourceforge.net>
  * @author   Demian Katz <demian.katz@villanova.edu>
  * @author   Luke O'Sullivan <l.osullivan@swansea.ac.uk>
@@ -28,10 +28,10 @@
  * @link     http://vufind.org/wiki/use_of_external_content Wiki
  */
 namespace VuFind\QRCode;
-use \PHPQRCode, Zend\Log\LoggerInterface;
+use \PHPQRCode;
 
 /**
- * Book Cover Generator
+ * QR Code Generator
  *
  * @category VuFind2
  * @package  QRCode_Generator
@@ -41,7 +41,7 @@ use \PHPQRCode, Zend\Log\LoggerInterface;
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     http://vufind.org/wiki/use_of_external_content Wiki
  */
-class Loader implements \Zend\Log\LoggerAwareInterface
+class Loader extends \VuFind\ImageLoader
 {
     /**
      * property to hold VuFind configuration settings
@@ -50,20 +50,6 @@ class Loader implements \Zend\Log\LoggerAwareInterface
      */
     protected $config;
 
-    /**
-     * Property for storing raw qrcode data; may be null if image is unavailable
-     *
-     * @var string
-     */
-    protected $qrcode = null;
-
-    /**
-     * Content type of data in $qrcode property
-     *
-     * @var string
-     */
-    protected $contentType = null;
-
     /**
      * The text used to generate the QRCode
      *
@@ -80,20 +66,6 @@ class Loader implements \Zend\Log\LoggerAwareInterface
         'level' => "L", 'size' => "3", 'margin' => "4"
     );
 
-    /**
-     * Logger (or false for none)
-     *
-     * @var LoggerInterface|bool
-     */
-    protected $logger = false;
-
-    /**
-     * Theme tools
-     *
-     * @var \VuFindTheme\ThemeInfo
-     */
-    protected $themeTools;
-
     /**
      * Constructor
      *
@@ -102,65 +74,15 @@ class Loader implements \Zend\Log\LoggerAwareInterface
      */
     public function __construct($config, \VuFindTheme\ThemeInfo $theme) {
         $this->config = $config;
-        $this->themeTools = $theme;
+        $this->setThemeInfo($theme);
+        $this->configuredFailImage
+            = isset($this->config->QRCode->noQRCodeAvailableImage)
+            ? $this->config->QRCode->noQRCodeAvailableImage : null;
+        $this->defaultFailImage = 'images/noQRCode.gif';
     }
 
     /**
-     * Set the logger
-     *
-     * @param LoggerInterface $logger Logger to use.
-     *
-     * @return void
-     */
-    public function setLogger(LoggerInterface $logger)
-    {
-        $this->logger = $logger;
-    }
-
-    /**
-     * Log a debug message.
-     *
-     * @param string $msg Message to log.
-     *
-     * @return void
-     */
-    protected function debug($msg)
-    {
-        if ($this->logger) {
-            $this->logger->debug($msg);
-        }
-    }
-
-    /**
-     * Get the QrCode data (usually called after loadQrCode)
-     *
-     * @return string
-     */
-    public function getQRCode()
-    {
-        // No image loaded?  Use "unavailable" as default:
-        if (is_null($this->qrcode)) {
-            $this->loadUnavailable();
-        }
-        return $this->image;
-    }
-
-    /**
-     * Get the content type of the current image (usually called after loadImage)
-     *
-     * @return string
-     */
-    public function getContentType()
-    {
-        // No content type loaded?  Use "unavailable" as default:
-        if (is_null($this->contentType)) {
-            $this->loadUnavailable();
-        }
-        return $this->contentType;
-    }
-
-    /**
-     * Load an image given an ISBN and/or content type.
+     * Set up a QR code image
      *
      * @param string $text   The QR code text
      * @param array  $params QR code parameters (level/size/margin)
@@ -179,9 +101,9 @@ class Loader implements \Zend\Log\LoggerAwareInterface
     }
 
     /**
-     * Load bookcover fom URL from cache or remote provider and display if possible.
+     * Generate a QR code image
      *
-     * @return bool        True if image displayed, false on failure.
+     * @return bool True if image displayed, false on failure.
      */
     protected function fetchQRCode()
     {
@@ -189,93 +111,10 @@ class Loader implements \Zend\Log\LoggerAwareInterface
             return false;
         }
         $this->contentType = 'image/png';
-        $this->qrcode = PHPQRCode\QRcode::PNG(
+        $this->image = PHPQRCode\QRcode::PNG(
             $this->text, false,
             $this->params['level'], $this->params['size'], $this->params['margin']
         );
         return true;
     }
-
-    /**
-     * Find a file in the themes (return false if no file exists).
-     *
-     * @param string $path    Relative path of file to find.
-     * @param array  $formats Optional array of suffixes to add to $path while
-     * searching theme (used to check multiple extensions in each theme).
-     *
-     * @return string|bool
-     */
-    protected function searchTheme($path, $formats = array(''))
-    {
-        // Check all supported image formats:
-        $filenames = array();
-        foreach ($formats as $format) {
-            $filenames[] =  $path . $format;
-        }
-        $fileMatch = $this->themeTools->findContainingTheme($filenames, true);
-        return empty($fileMatch) ? false : $fileMatch;
-    }
-
-    /**
-     * Load the user-specified "cover unavailable" graphic (or default if none
-     * specified).
-     *
-     * @return void
-     * @author Thomas Schwaerzler <vufind-tech@lists.sourceforge.net>
-     */
-    public function loadUnavailable()
-    {
-        // Get "no qrcode" image from config.ini:
-        $noQRCodeImage = isset($this->config->QRCode->noQRCodeAvailableImage )
-            ? $this->searchTheme($this->config->QRCode->noQRCodeAvailableImage)
-            : null;
-
-        // No setting -- use default, and don't log anything:
-        if (empty($noQRCodeImage)) {
-            // log?
-            return $this->loadDefaultFailImage();
-        }
-
-        // If file defined but does not exist, log error and display default:
-        if (!file_exists($noQRCodeImage) || !is_readable($noQRCodeImage)) {
-            $this->debug("Cannot access file: '$noQRCodeImage'");
-            return $this->loadDefaultFailImage();
-        }
-
-        // Array containing map of allowed file extensions to mimetypes
-        // (to be extended)
-        $allowedFileExtensions = array(
-            "gif" => "image/gif",
-            "jpeg" => "image/jpeg", "jpg" => "image/jpeg",
-            "png" => "image/png",
-            "tiff" => "image/tiff", "tif" => "image/tiff"
-        );
-
-        // Log error and bail out if file lacks a known image extension:
-        $parts = explode('.', $noQRCodeImage);
-        $fileExtension = strtolower(end($parts));
-        if (!array_key_exists($fileExtension, $allowedFileExtensions)) {
-            $this->debug(
-                "Illegal file-extension '$fileExtension' for image '$noQRCodeImage'"
-            );
-            return $this->loadDefaultFailImage();
-        }
-
-        // Get mime type from file extension:
-        $this->contentType = $allowedFileExtensions[$fileExtension];
-
-        // Load the image data:
-        $this->image = file_get_contents($noQRCodeImage);
-    }
-
-    /**
-     * Display the default "qrcode unavailable" graphic and terminate execution.
-     *
-     * @return void
-     */
-    protected function loadDefaultFailImage()
-    {
-        $this->contentType = 'image/gif';
-        $this->image = file_get_contents($this->searchTheme('images/noQRCode.gif'));
-    }
 }
diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/QRCode/LoaderTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/QRCode/LoaderTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..fa67a1b2b0ff13c5209697748e38ba6d506c92cb
--- /dev/null
+++ b/module/VuFind/tests/unit-tests/src/VuFindTest/QRCode/LoaderTest.php
@@ -0,0 +1,108 @@
+<?php
+/**
+ * QR Code 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\QRCode;
+use VuFind\QRCode\Loader;
+use VuFindTheme\ThemeInfo;
+use Zend\Config\Config;
+
+/**
+ * QR Code 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 LoaderTest extends \VuFindTest\Unit\TestCase
+{
+    /**
+     * Theme to use for testing purposes.
+     *
+     * @var string
+     */
+    protected $testTheme = 'bootstrap3';
+
+    /**
+     * Test that failure to load even the baseline image causes an exception.
+     *
+     * @return void
+     * @expectedException Exception
+     * @expectedExceptionMessage Could not load default fail image.
+     */
+    public function testUtterFailure()
+    {
+        $theme = $this->getMock('VuFindTheme\ThemeInfo', array(), array('foo', 'bar'));
+        $theme->expects($this->once())->method('findContainingTheme')->with($this->equalTo(array('images/noQRCode.gif')))->will($this->returnValue(false));
+        $loader = $this->getLoader(array(), $theme);
+        $loader->getImage();
+    }
+
+    /**
+     * Test that requesting a blank QR code returns the fail image.
+     *
+     * @return void
+     */
+    public function testDefaultLoadingForBlankText()
+    {
+        $loader = $this->getLoader();
+        $this->assertEquals('image/gif', $loader->getContentType());
+        $this->assertEquals('483', strlen($loader->getImage()));
+    }
+
+    /**
+     * Get a loader object to test.
+     *
+     * @param array      $config  Configuration
+     * @param ThemeInfo  $theme   Theme info object (null to create default)
+     * @param array|bool $mock    Array of functions to mock, or false for real object
+     *
+     * @return void
+     */
+    protected function getLoader($config = array(), $theme = null, $mock = false)
+    {
+        $config = new Config($config);
+        if (null === $theme) {
+            $theme = new ThemeInfo($this->getThemeDir(), $this->testTheme);
+        }
+        if ($mock) {
+            return $this->getMock('VuFind\QRCode\Loader', $mock, array($config, $theme));
+        }
+        return new Loader($config, $theme);
+    }
+
+    /**
+     * Get the theme directory.
+     *
+     * @return string
+     */
+    protected function getThemeDir()
+    {
+        return realpath(__DIR__ . '/../../../../../../../themes');
+    }
+}
\ No newline at end of file