diff --git a/src/Tests/SchedulerApiTestCase.php b/src/Tests/SchedulerApiTestCase.php
index 01ee15c..be2ad58 100644
--- a/src/Tests/SchedulerApiTestCase.php
+++ b/src/Tests/SchedulerApiTestCase.php
@@ -59,26 +59,28 @@ class SchedulerApiTestCase extends SchedulerTestBase {
    * Tests hook_scheduler_allow().
    *
    * This hook can allow or deny the (un)publication of individual nodes. This
-   * test uses a content type which has a checkbox 'Approved for publication'.
-   * The node may only be published if this checkbox is ticked.
+   * test uses a content type which has checkboxes 'Approved for publication'
+   * and 'Approved for unpublication'. The node may only be published or 
+   * unpublished if the appropriate checkbox is ticked.
    *
    * @todo Create and update the nodes through the interface so we can check if
    *   the correct messages are displayed.
    */
-  public function testAllowedPublishing() {
+  public function testAllowedPublishingAndUnpublishing() {
     if (empty($this->nodetype)) {
       $this->fail('*** Custom node type ' . $this->custom_type . ' does not exist. Testing abandoned ***');
       return;
     }
 
-    // Check that the approved field is shown on the node/add form.
+    // Check that the approved fields are shown on the node/add form.
     $this->drupalLogin($this->webUser);
     $this->drupalGet('node/add/' . $this->custom_type);
-    $this->assertFieldById('edit-field-approved-value', '', 'The Approved field is shown on the node form');
+    $this->assertFieldById('edit-field-approved-publishing-value', '', 'The "Approved for publishing" field is shown on the node form');
+    $this->assertFieldById('edit-field-approved-unpublishing-value', '', 'The "Approved for unpublishing" field is shown on the node form');
 
     // Create a node that is scheduled but not approved for publication. Then
     // simulate a cron run, and check that the node is still not published.
-    $node = $this->createUnapprovedNode();
+    $node = $this->createUnapprovedNode('publish_on');
     scheduler_cron();
     $this->node_storage->resetCache(array($node->id()));
     $node = $this->node_storage->load($node->id());
@@ -86,7 +88,7 @@ class SchedulerApiTestCase extends SchedulerTestBase {
 
     // Approve the node for publication, simulate a cron run and check that the
     // node is now published.
-    $this->approveNode($node->id());
+    $this->approveNode($node->id(), 'field_approved_publishing');
     scheduler_cron();
     $this->node_storage->resetCache(array($node->id()));
     $node = $this->node_storage->load($node->id());
@@ -95,52 +97,79 @@ class SchedulerApiTestCase extends SchedulerTestBase {
     // Turn on immediate publication of nodes with publication dates in the past
     // and repeat the tests. It is not needed to simulate cron runs here.
     $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date', 'publish')->save();
-    $node = $this->createUnapprovedNode();
+    $node = $this->createUnapprovedNode('publish_on');
     $this->assertFalse($node->isPublished(), 'An unapproved node with a date in the past is not published immediately after saving.');
 
     // Check that the node can be approved and published programatically.
-    $this->approveNode($node->id());
+    $this->approveNode($node->id(), 'field_approved_publishing');
     $this->node_storage->resetCache(array($node->id()));
     $node = $this->node_storage->load($node->id());
     $this->assertTrue($node->isPublished(), 'An approved node with a date in the past is published immediately via $node->set()->save().');
 
     // Check that a node can be approved and published via edit form.
-    $node = $this->createUnapprovedNode();
-    $this->drupalPostForm('node/' . $node->id() . '/edit', ['field_approved[value]' => '1'], t('Save'));
+    $node = $this->createUnapprovedNode('publish_on');
+    $this->drupalPostForm('node/' . $node->id() . '/edit', ['field_approved_publishing[value]' => '1'], t('Save'));
     $this->node_storage->resetCache(array($node->id()));
     $node = $this->node_storage->load($node->id());
     $this->assertTrue($node->isPublished(), 'An approved node with a date in the past is published immediately after saving via edit form.');
+
+    // Test approval for unpublishing. This is simpler than the test sequence
+    // for publishing, because the 'immediate' option is not applicable.
+
+    // Create a node that is scheduled but not approved for unpublication. Then
+    // simulate a cron run, and check that the node is still published.
+    $node = $this->createUnapprovedNode('unpublish_on');
+    scheduler_cron();
+    $this->node_storage->resetCache(array($node->id()));
+    $node = $this->node_storage->load($node->id());
+    $this->assertTrue($node->isPublished(), 'An unapproved node is not unpublished during cron processing.');
+
+    // Approve the node for unpublishing, simulate a cron run and check that
+    // the node is now unpublished.
+    $this->approveNode($node->id(), 'field_approved_unpublishing');
+    scheduler_cron();
+    $this->node_storage->resetCache(array($node->id()));
+    $node = $this->node_storage->load($node->id());
+    $this->assertFalse($node->isPublished(), 'An approved node is unpublished during cron processing.');
   }
 
   /**
-   * Creates a new node that is not approved by the CEO.
+   * Creates a new node that is not approved.
    *
-   * The node has a publication date in the past to make sure it will be
+   * The node has a publish/unpublish date in the past to make sure it will be
    * included in the next cron run.
    *
+   * @param string $date_field
+   *   The Scheduler date field to set, either 'publish_on' or 'unpublish_on'.
+   *
    * @return \Drupal\node\NodeInterface
    *   A node object.
    */
-  protected function createUnapprovedNode() {
+  protected function createUnapprovedNode($date_field) {
     $settings = array(
-      'status' => 0,
-      'publish_on' => strtotime('-1 day'),
-      'field_approved' => 0,
+      'status' => ($date_field == 'unpublish_on'),
+      $date_field => strtotime('-1 day'),
+      'field_approved_publishing' => 0,
+      'field_approved_unpublishing' => 0,
       'type' => $this->custom_type,
     );
     return $this->drupalCreateNode($settings);
   }
 
   /**
-   * Approves a node for publication.
+   * Approves a node for publication or unpublication.
    *
    * @param int $nid
    *   The id of the node to approve.
+   *
+   * @param string $field_name
+   *   The name of the field to set, either 'field_approved_publishing' or
+   *   'field_approved_for_unpublishing'.
    */
-  protected function approveNode($nid) {
+  protected function approveNode($nid, $field_name) {
     $this->node_storage->resetCache(array($nid));
     $node = $this->node_storage->load($nid);
-    $node->set('field_approved' , TRUE)->save();
+    $node->set($field_name, TRUE)->save();
   }
 
 }
diff --git a/tests/modules/config/install/core.entity_form_display.node.scheduler_api_test.default.yml b/tests/modules/config/install/core.entity_form_display.node.scheduler_api_test.default.yml
index ffd242a..e943f58 100644
--- a/tests/modules/config/install/core.entity_form_display.node.scheduler_api_test.default.yml
+++ b/tests/modules/config/install/core.entity_form_display.node.scheduler_api_test.default.yml
@@ -2,7 +2,8 @@ langcode: en
 status: true
 dependencies:
   config:
-    - field.field.node.scheduler_api_test.field_approved
+    - field.field.node.scheduler_api_test.field_approved_publishing
+    - field.field.node.scheduler_api_test.field_approved_unpublishing
     - node.type.scheduler_api_test
   module:
     - path
@@ -54,20 +55,26 @@ content:
     weight: 6
     settings: {  }
     third_party_settings: {  }
-  field_approved:
+  field_approved_publishing:
     type: boolean_checkbox
     weight: 7
     settings:
-      display_label: false
+      display_label: true
+    third_party_settings: {  }
+  field_approved_unpublishing:
+    type: boolean_checkbox
+    weight: 8
+    settings:
+      display_label: true
     third_party_settings: {  }
   publish_on:
     type: datetime_timestamp_no_default
-    weight: 8
+    weight: 9
     settings: {  }
     third_party_settings: {  }
   unpublish_on:
     type: datetime_timestamp_no_default
-    weight: 9
+    weight: 10
     settings: {  }
     third_party_settings: {  }
 hidden: {  }
diff --git a/tests/modules/config/install/core.entity_view_display.node.scheduler_api_test.default.yml b/tests/modules/config/install/core.entity_view_display.node.scheduler_api_test.default.yml
index d63432d..34011b1 100644
--- a/tests/modules/config/install/core.entity_view_display.node.scheduler_api_test.default.yml
+++ b/tests/modules/config/install/core.entity_view_display.node.scheduler_api_test.default.yml
@@ -2,7 +2,8 @@ langcode: en
 status: true
 dependencies:
   config:
-    - field.field.node.scheduler_api_test.field_approved
+    - field.field.node.scheduler_api_test.field_approved_publishing
+    - field.field.node.scheduler_api_test.field_approved_unpublishing
     - node.type.scheduler_api_test
   module:
     - user
@@ -11,7 +12,16 @@ targetEntityType: node
 bundle: scheduler_api_test
 mode: default
 content:
-  field_approved:
+  field_approved_publishing:
+    type: boolean
+    weight: 1
+    label: inline
+    settings:
+      format: default
+      format_custom_false: ''
+      format_custom_true: ''
+    third_party_settings: {  }
+  field_approved_unpublishing:
     type: boolean
     weight: 1
     label: inline
diff --git a/tests/modules/config/install/field.storage.node.field_approved_publishing.yml b/tests/modules/config/install/field.storage.node.field_approved_publishing.yml
new file mode 100644
index 0000000..a4b21ce
--- /dev/null
+++ b/tests/modules/config/install/field.storage.node.field_approved_publishing.yml
@@ -0,0 +1,17 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - node
+id: node.field_approved_publishing
+field_name: field_approved_publishing
+entity_type: node
+type: boolean
+settings: {  }
+module: core
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/tests/modules/config/install/field.storage.node.field_approved_unpublishing.yml b/tests/modules/config/install/field.storage.node.field_approved_unpublishing.yml
new file mode 100644
index 0000000..f6bdc70
--- /dev/null
+++ b/tests/modules/config/install/field.storage.node.field_approved_unpublishing.yml
@@ -0,0 +1,17 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - node
+id: node.field_approved_unpublishing
+field_name: field_approved_unpublishing
+entity_type: node
+type: boolean
+settings: {  }
+module: core
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/tests/modules/config/install/field.field.node.scheduler_api_test.field_approved_publishing.yml b/tests/modules/config/install/field.field.node.scheduler_api_test.field_approved_publishing.yml
new file mode 100644
index 0000000..2e98706
--- /dev/null
+++ b/tests/modules/config/install/field.field.node.scheduler_api_test.field_approved_publishing.yml
@@ -0,0 +1,22 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.node.field_approved_publishing
+    - node.type.scheduler_api_test
+id: node.scheduler_api_test.field_approved_publishing
+field_name: field_approved_publishing
+entity_type: node
+bundle: scheduler_api_test
+label: 'Approved for Publishing'
+description: ''
+required: false
+translatable: false
+default_value:
+  -
+    value: 0
+default_value_callback: ''
+settings:
+  on_label: 'Yes'
+  off_label: 'No'
+field_type: boolean
diff --git a/tests/modules/config/install/field.field.node.scheduler_api_test.field_approved_unpublishing.yml b/tests/modules/config/install/field.field.node.scheduler_api_test.field_approved_unpublishing.yml
new file mode 100644
index 0000000..ef02a10
--- /dev/null
+++ b/tests/modules/config/install/field.field.node.scheduler_api_test.field_approved_unpublishing.yml
@@ -0,0 +1,22 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.node.field_approved_unpublishing
+    - node.type.scheduler_api_test
+id: node.scheduler_api_test.field_approved_unpublishing
+field_name: field_approved_unpublishing
+entity_type: node
+bundle: scheduler_api_test
+label: 'Approved for Unpublishing'
+description: ''
+required: false
+translatable: false
+default_value:
+  -
+    value: 0
+default_value_callback: ''
+settings:
+  on_label: 'Yes'
+  off_label: 'No'
+field_type: boolean
diff --git a/tests/modules/scheduler_api_test.install b/tests/modules/scheduler_api_test.install
index d4707f6..a5eeaa1 100644
--- a/tests/modules/scheduler_api_test.install
+++ b/tests/modules/scheduler_api_test.install
@@ -13,12 +13,28 @@ function scheduler_api_test_install() {
 
 /**
  * Implements hook_uninstall().
+ *
+ * Although not strictly necessary for testing, it is good practice to tidy up
+ * module config and content on uninstalling. Plus, when developing this module
+ * and enabling it manually as a real module, this code is needed to clean up,
+ * otherwise a re-install fails.
  */
 function scheduler_api_test_uninstall() {
+
+  // Delete any content that may have been created for the custom node type.
+  $nids_query = db_select('node', 'n')
+    ->fields('n', array('nid'))
+    ->condition('n.type', ['scheduler_api_test'], 'IN')
+    ->execute();
+  $nids = $nids_query->fetchCol();
+  entity_delete_multiple('node', $nids);
+
   // Delete the custom node type and other config items.
   \Drupal::configFactory()->getEditable('node.type.scheduler_api_test')->delete();
-  \Drupal::configFactory()->getEditable('field.storage.node.field_approved')->delete();
-  \Drupal::configFactory()->getEditable('field.field.node.scheduler_api_test.field_approved')->delete();
+  \Drupal::configFactory()->getEditable('field.storage.node.field_approved_publishing')->delete();
+  \Drupal::configFactory()->getEditable('field.storage.node.field_approved_unpublishing')->delete();
+  \Drupal::configFactory()->getEditable('field.field.node.scheduler_api_test.field_approved_publishing')->delete();
+  \Drupal::configFactory()->getEditable('field.field.node.scheduler_api_test.field_approved_unpublishing')->delete();
   \Drupal::configFactory()->getEditable('core.entity_form_display.node.scheduler_api_test.default')->delete();
   \Drupal::configFactory()->getEditable('core.entity_view_display.node.scheduler_api_test.default')->delete();
   // @todo Is there a better way to delete the config items on uninstall?
diff --git a/tests/modules/scheduler_api_test.module b/tests/modules/scheduler_api_test.module
index 1bada35..e81f756 100644
--- a/tests/modules/scheduler_api_test.module
+++ b/tests/modules/scheduler_api_test.module
@@ -10,16 +10,59 @@
  */
 function scheduler_api_test_scheduler_allow_publishing($node) {
   // If there is no 'approved' field do nothing to change the result.
-  if (!isset($node->field_approved)) {
+  if (!isset($node->field_approved_publishing)) {
     $allowed = TRUE;
   }
   else {
     // Only publish nodes that have the 'Approved' checkbox ticked.
-    $allowed = $node->field_approved->value;
+    $allowed = $node->field_approved_publishing->value;
     // If publication is denied then inform the user why.
     if (!$allowed) {
-      drupal_set_message(t('The content can be scheduled, but will only be published after approval by the CEO.'), 'status', FALSE);
+      drupal_set_message(t('The content can be scheduled for publishing, but will not be published until approved by the CEO.'), 'status', FALSE);
     }
   }
   return $allowed;
 }
+
+/**
+ * Implements hook_scheduler_allow_unpublishing().
+ */
+function scheduler_api_test_scheduler_allow_unpublishing($node) {
+  // If there is no 'approved' field do nothing to change the result.
+  if (!isset($node->field_approved_unpublishing)) {
+    $allowed = TRUE;
+  }
+  else {
+    // Only unpublish nodes that have the 'Approved Unpublishing' checkbox ticked.
+    $allowed = $node->field_approved_unpublishing->value;
+    // If unpublication is denied then inform the user why.
+    if (!$allowed) {
+      drupal_set_message(t('The content can be scheduled for unpublishing, but will not be unpublished until approved by the CEO.'), 'status', FALSE);
+    }
+  }
+  return $allowed;
+}
+
+/**
+ * Implements hook_scheduler_api().
+ */
+function scheduler_api_test_scheduler_api($node, $action) {
+  // @todo Add something here.
+}
+
+/**
+ * Implements hook_scheduler_nid_list().
+ */
+function scheduler_api_test_scheduler_nid_list($action) {
+  // @todo Add something here.
+  $nids = [];    
+  return $nids;
+}
+
+/**
+ * Implements hook_scheduler_nid_list_alter().
+ */
+function scheduler_api_test_scheduler_nid_list_alter($nids, $action) {
+  // @todo Add something here.
+  return $nids;
+}
