diff --git a/config/vufind/Demo.ini b/config/vufind/Demo.ini
index 15949060d5e3989e5b7612892be990af729d6686..1033570e85018a50409e339499e031974bc2b630 100644
--- a/config/vufind/Demo.ini
+++ b/config/vufind/Demo.ini
@@ -23,6 +23,11 @@ services[] = 'loan'
 services[] = 'presentation'
 services[] = 'custom'
 
+; This setting can be used to create fake checked out items for specific records.
+; The value is a JSON document representing the status information returned by the
+; driver.
+;transactions = '[{"id":"1234", ... "renewable": true}]';
+
 ; This section can be used to create a set of fake users recognized by the
 ; Demo driver. If it is uncommented, only usernames and passwords listed here
 ; will be recognized for ILS login. If it is commented out, all username/password
@@ -57,6 +62,7 @@ cancelStorageRetrievalRequests = 50
 changePassword = 33
 checkILLRequestBlock = 10
 checkILLRequestIsValid = 10
+checkRenewBlock = 25
 checkRequestBlock = 10
 checkRequestIsValid = 10
 checkStorageRetrievalRequestBlock = 10
diff --git a/module/VuFind/src/VuFind/ILS/Driver/Demo.php b/module/VuFind/src/VuFind/ILS/Driver/Demo.php
index 2a1186e11e69df93bea1e8b4b2de6727b79844d2..4878e89d813c784a52dce97ee6e703ab057f9ee4 100644
--- a/module/VuFind/src/VuFind/ILS/Driver/Demo.php
+++ b/module/VuFind/src/VuFind/ILS/Driver/Demo.php
@@ -291,6 +291,16 @@ class Demo extends AbstractBase
             ? $this->config['Records']['source'] : DEFAULT_SEARCH_BACKEND;
     }
 
+    /**
+     * Are renewals blocked?
+     *
+     * @return bool
+     */
+    protected function checkRenewBlock()
+    {
+        return $this->isFailing(__METHOD__, 25);
+    }
+
     /**
      * Are holds/recalls blocked?
      *
@@ -822,6 +832,103 @@ class Demo extends AbstractBase
         return $session->ILLRequests;
     }
 
+    /**
+     * Construct a transaction list for getMyTransactions; may be random or
+     * pre-set depending on Demo.ini settings.
+     *
+     * @return array
+     */
+    protected function getTransactionList()
+    {
+        // If Demo.ini includes a fixed set of transactions, load those; otherwise
+        // build some random ones.
+        return isset($this->config['Records']['transactions'])
+            ? json_decode($this->config['Records']['transactions'], true)
+            : $this->getRandomTransactionList();
+    }
+
+    /**
+     * Construct a random set of transactions for getMyTransactions().
+     *
+     * @return array
+     */
+    protected function getRandomTransactionList()
+    {
+        // How many items are there?  %10 - 1 = 10% chance of none,
+        // 90% of 1-9 (give or take some odd maths)
+        $trans = rand() % 10 - 1;
+
+        $transList = [];
+        for ($i = 0; $i < $trans; $i++) {
+            // When is it due? +/- up to 15 days
+            $due_relative = rand() % 30 - 15;
+            // Due date
+            $dueStatus = false;
+            if ($due_relative >= 0) {
+                $rawDueDate = strtotime("now +$due_relative days");
+                if ($due_relative == 0) {
+                    $dueStatus = 'due';
+                }
+            } else {
+                $rawDueDate = strtotime("now $due_relative days");
+                $dueStatus = 'overdue';
+            }
+
+            // Times renewed    : 0,0,0,0,0,1,2,3,4,5
+            $renew = rand() % 10 - 5;
+            if ($renew < 0) {
+                $renew = 0;
+            }
+
+            // Renewal limit
+            $renewLimit = $renew + rand() % 3;
+
+            // Pending requests : 0,0,0,0,0,1,2,3,4,5
+            $req = rand() % 10 - 5;
+            if ($req < 0) {
+                $req = 0;
+            }
+
+            // Create a generic transaction:
+            $transList[] = $this->getRandomItemIdentifier() + [
+                // maintain separate display vs. raw due dates (the raw
+                // one is used for renewals, in case the user display
+                // format is incompatible with date math).
+                'duedate' => $this->dateConverter->convertToDisplayDate(
+                    'U', $rawDueDate
+                ),
+                'rawduedate' => $rawDueDate,
+                'dueStatus' => $dueStatus,
+                'barcode' => sprintf("%08d", rand() % 50000),
+                'renew'   => $renew,
+                'renewLimit' => $renewLimit,
+                'request' => $req,
+                'item_id' => $i,
+                'renewable' => $renew < $renewLimit,
+            ];
+            if ($i == 2 || rand() % 5 == 1) {
+                // Mimic an ILL loan
+                $transList[$i] += [
+                    'id'      => "ill_institution_$i",
+                    'title'   => "ILL Loan Title $i",
+                    'institution_id' => 'ill_institution',
+                    'institution_name' => 'ILL Library',
+                    'institution_dbkey' => 'ill_institution',
+                    'borrowingLocation' => 'ILL Service Desk'
+                ];
+            } else {
+                $transList[$i]['borrowingLocation'] = $this->getFakeLoc();
+                if ($this->idsInMyResearch) {
+                    $transList[$i]['id'] = $this->getRandomBibId();
+                    $transList[$i]['source'] = $this->getRecordSource();
+                } else {
+                    $transList[$i]['title'] = 'Demo Title ' . $i;
+                }
+            }
+            return $transList;
+        }
+    }
+
     /**
      * Get Patron Transactions
      *
@@ -838,79 +945,7 @@ class Demo extends AbstractBase
     {
         $session = $this->getSession();
         if (!isset($session->transactions)) {
-            // How many items are there?  %10 - 1 = 10% chance of none,
-            // 90% of 1-9 (give or take some odd maths)
-            $trans = rand() % 10 - 1;
-
-            $transList = [];
-            for ($i = 0; $i < $trans; $i++) {
-                // When is it due? +/- up to 15 days
-                $due_relative = rand() % 30 - 15;
-                // Due date
-                $dueStatus = false;
-                if ($due_relative >= 0) {
-                    $rawDueDate = strtotime("now +$due_relative days");
-                    if ($due_relative == 0) {
-                        $dueStatus = 'due';
-                    }
-                } else {
-                    $rawDueDate = strtotime("now $due_relative days");
-                    $dueStatus = 'overdue';
-                }
-
-                // Times renewed    : 0,0,0,0,0,1,2,3,4,5
-                $renew = rand() % 10 - 5;
-                if ($renew < 0) {
-                    $renew = 0;
-                }
-
-                // Renewal limit
-                $renewLimit = $renew + rand() % 3;
-
-                // Pending requests : 0,0,0,0,0,1,2,3,4,5
-                $req = rand() % 10 - 5;
-                if ($req < 0) {
-                    $req = 0;
-                }
-
-                // Create a generic transaction:
-                $transList[] = $this->getRandomItemIdentifier() + [
-                    // maintain separate display vs. raw due dates (the raw
-                    // one is used for renewals, in case the user display
-                    // format is incompatible with date math).
-                    'duedate' => $this->dateConverter->convertToDisplayDate(
-                        'U', $rawDueDate
-                    ),
-                    'rawduedate' => $rawDueDate,
-                    'dueStatus' => $dueStatus,
-                    'barcode' => sprintf("%08d", rand() % 50000),
-                    'renew'   => $renew,
-                    'renewLimit' => $renewLimit,
-                    'request' => $req,
-                    'item_id' => $i,
-                    'renewable' => $renew < $renewLimit,
-                ];
-                if ($i == 2 || rand() % 5 == 1) {
-                    // Mimic an ILL loan
-                    $transList[$i] += [
-                        'id'      => "ill_institution_$i",
-                        'title'   => "ILL Loan Title $i",
-                        'institution_id' => 'ill_institution',
-                        'institution_name' => 'ILL Library',
-                        'institution_dbkey' => 'ill_institution',
-                        'borrowingLocation' => 'ILL Service Desk'
-                    ];
-                } else {
-                    $transList[$i]['borrowingLocation'] = $this->getFakeLoc();
-                    if ($this->idsInMyResearch) {
-                        $transList[$i]['id'] = $this->getRandomBibId();
-                        $transList[$i]['source'] = $this->getRecordSource();
-                    } else {
-                        $transList[$i]['title'] = 'Demo Title ' . $i;
-                    }
-                }
-            }
-            $session->transactions = $transList;
+            $session->transactions = $this->getTransactionList();
         }
         return $session->transactions;
     }
@@ -1304,7 +1339,7 @@ class Demo extends AbstractBase
     public function renewMyItems($renewDetails)
     {
         // Simulate an account block at random.
-        if (rand() % 4 == 1) {
+        if ($this->checkRenewBlock()) {
             return [
                 'blocks' => [
                     'Simulated account block; try again and it will work eventually.'
diff --git a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/IlsActionsTest.php b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/IlsActionsTest.php
index 42a8efa1ecdbc42781a7222292d387184352df75..21e8f7f18b833677da2eef9623925d39644ee729 100644
--- a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/IlsActionsTest.php
+++ b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/IlsActionsTest.php
@@ -76,10 +76,40 @@ class IlsActionsTest extends \VuFindTest\Unit\MinkTestCase
                 'driver' => 'Demo',
                 'holds_mode' => 'driver',
                 'title_level_holds_mode' => 'driver',
+                'renewals_enabled' => true,
             ]
         ];
     }
 
+    /**
+     * Get transaction JSON for Demo.ini.
+     *
+     * @param string $bibId Bibliographic record ID to create fake item info for.
+     *
+     * @return array
+     */
+    protected function getFakeTransactions($bibId)
+    {
+        $rawDueDate = strtotime("now +5 days");
+        return json_encode(
+            [
+                [
+                    'duedate' => $rawDueDate,
+                    'rawduedate' => $rawDueDate,
+                    'dueStatus' => 'due',
+                    'barcode' => 1234567890,
+                    'renew'   => 0,
+                    'renewLimit' => 1,
+                    'request' => 0,
+                    'id' => $bibId,
+                    'source' => 'Solr',
+                    'item_id' => 0,
+                    'renewable' => true,
+                ]
+            ]
+        );
+    }
+
     /**
      * Get Demo.ini override settings for testing ILS functions.
      *
@@ -90,12 +120,16 @@ class IlsActionsTest extends \VuFindTest\Unit\MinkTestCase
     public function getDemoIniOverrides($bibId = 'testsample1')
     {
         return [
+            'Records' => [
+                'transactions' => $this->getFakeTransactions($bibId),
+            ],
             'Failure_Probabilities' => [
                 'cancelHolds' => 0,
                 'cancelILLRequests' => 0,
                 'cancelStorageRetrievalRequests' => 0,
                 'checkILLRequestBlock' => 0,
                 'checkILLRequestIsValid' => 0,
+                'checkRenewBlock' => 0,
                 'checkRequestBlock' => 0,
                 'checkRequestIsValid' => 0,
                 'checkStorageRetrievalRequestBlock' => 0,
@@ -105,6 +139,7 @@ class IlsActionsTest extends \VuFindTest\Unit\MinkTestCase
                 'placeHold' => 0,
                 'placeILLRequest' => 0,
                 'placeStorageRetrievalRequest' => 0,
+                'renewMyItems' => 0,
             ],
             'Holdings' => [$bibId => json_encode([$this->getFakeItem()])],
             'Users' => ['catuser' => 'catpass'],
@@ -485,6 +520,46 @@ class IlsActionsTest extends \VuFindTest\Unit\MinkTestCase
         }
     }
 
+    /**
+     * Test renewal action.
+     *
+     * @return void
+     */
+    public function testRenewal()
+    {
+        $this->changeConfigs(
+            [
+                'config' => $this->getConfigIniOverrides(),
+                'Demo' => $this->getDemoIniOverrides(),
+            ]
+        );
+
+        // Go to user profile screen:
+        $session = $this->getMinkSession();
+        $session->visit($this->getVuFindUrl() . '/MyResearch/CheckedOut');
+        $page = $session->getPage();
+
+        // Log in
+        $this->fillInLoginForm($page, 'username1', 'test', false);
+        $this->submitLoginForm($page, false);
+
+        // Test submitting with no selected checkboxes:
+        $this->findCss($page, '#renewSelected')->click();
+        $this->snooze();
+        $this->assertEquals(
+            'No items were selected',
+            $this->findCss($page, '.alert.alert-danger')->getText()
+        );
+
+        // Test "renew all":
+        $this->findCss($page, '#renewAll')->click();
+        $this->snooze();
+        $this->assertEquals(
+            'Renewal Successful',
+            $this->findCss($page, '.alert.alert-success')->getText()
+        );
+    }
+
     /**
      * Standard teardown method.
      *
diff --git a/themes/bootstrap3/templates/myresearch/checkedout.phtml b/themes/bootstrap3/templates/myresearch/checkedout.phtml
index 7cf480069f2c01ff0b4f1b94ff05b716711ba9da..bbcaf47496a53d086cfa856514fb11591ee37687 100644
--- a/themes/bootstrap3/templates/myresearch/checkedout.phtml
+++ b/themes/bootstrap3/templates/myresearch/checkedout.phtml
@@ -22,8 +22,8 @@
               <input type="checkbox" name="selectAll" class="checkbox-select-all"/>
               <?=$this->transEsc('select_page')?> |
             </label>
-            <input type="submit" class="btn btn-default" name="renewSelected" value="<?=$this->transEsc("renew_selected")?>" />
-            <input type="submit" class="btn btn-default" name="renewAll" value="<?=$this->transEsc('renew_all')?>" />
+            <input type="submit" class="btn btn-default" id="renewSelected" name="renewSelected" value="<?=$this->transEsc("renew_selected")?>" />
+            <input type="submit" class="btn btn-default" id="renewAll" name="renewAll" value="<?=$this->transEsc('renew_all')?>" />
           </div>
         </div>
       <? endif; ?>