diff --git a/CRM/Member/BAO/Membership.php b/CRM/Member/BAO/Membership.php
index c927be1aff4a24cb6288002bce62e7b033cce411..4a4ed1ca5b6b50f45610ff86ba3359cb6d5db0e2 100644
--- a/CRM/Member/BAO/Membership.php
+++ b/CRM/Member/BAO/Membership.php
@@ -2164,21 +2164,50 @@ INNER JOIN  civicrm_contact contact ON ( contact.id = membership.contact_id AND
    * IMPORTANT:
    * Sending renewal reminders has been migrated from this job to the Scheduled Reminders function as of 4.3.
    *
+   * @param array $params
+   *   only_active_membership_types, exclude_test_memberships, exclude_membership_status_ids
+   *
    * @return array
    *
    * @throws \CiviCRM_API3_Exception
    * @throws \CRM_Core_Exception
    */
-  public static function updateAllMembershipStatus() {
-    // Tests for this function are in api_v3_JobTest. Please add tests for all updates.
-
-    $updateCount = $processCount = self::updateDeceasedMembersStatuses();
-
+  public static function updateAllMembershipStatus($params = []) {
     // We want all of the statuses as id => name, even the disabled ones (cf.
     // CRM-15475), to identify which are Pending, Deceased, Cancelled, and
     // Expired.
     $allStatus = CRM_Member_BAO_Membership::buildOptions('status_id', 'validate');
-    $allTypes = CRM_Member_PseudoConstant::membershipType();
+    if (empty($params['exclude_membership_status_ids'])) {
+      $params['exclude_membership_status_ids'] = [
+        array_search('Pending', $allStatus),
+        array_search('Cancelled', $allStatus),
+        array_search('Expired', $allStatus) ?: 0,
+        array_search('Deceased', $allStatus),
+      ];
+    }
+    // Deceased is *always* excluded because it is has very specific processing below.
+    elseif (!in_array(array_search('Deceased', $allStatus), $params['exclude_membership_status_ids'])) {
+      $params['exclude_membership_status_ids'][] = array_search('Deceased', $allStatus);
+    }
+
+    for ($index = 0; $index < count($params['exclude_membership_status_ids']); $index++) {
+      $queryParams[$index] = [$params['exclude_membership_status_ids'][$index], 'Integer'];
+    }
+    $membershipStatusClause = 'civicrm_membership.status_id NOT IN (%' . implode(', %', array_keys($queryParams)) . ')';
+
+    // Tests for this function are in api_v3_JobTest. Please add tests for all updates.
+
+    $updateCount = $processCount = self::updateDeceasedMembersStatuses();
+
+    $whereClauses[] = 'civicrm_contact.is_deceased = 0';
+    if ($params['exclude_test_memberships']) {
+      $whereClauses[] = 'civicrm_membership.is_test = 0';
+    }
+    $whereClause = implode(' AND ', $whereClauses);
+    $activeMembershipClause = '';
+    if ($params['only_active_membership_types']) {
+      $activeMembershipClause = ' AND civicrm_membership_type.is_active = 1';
+    }
 
     // This query retrieves ALL memberships of active types.
     $baseQuery = "
@@ -2197,15 +2226,8 @@ SELECT     civicrm_membership.id                    as membership_id,
 FROM       civicrm_membership
 INNER JOIN civicrm_contact ON ( civicrm_membership.contact_id = civicrm_contact.id )
 INNER JOIN civicrm_membership_type ON
-  (civicrm_membership.membership_type_id = civicrm_membership_type.id AND civicrm_membership_type.is_active = 1)
-WHERE      civicrm_membership.is_test = 0
-           AND civicrm_contact.is_deceased = 0 ";
-
-    $deceaseStatusId = array_search('Deceased', $allStatus);
-    $pendingStatusId = array_search('Pending', $allStatus);
-    $cancelledStatusId = array_search('Cancelled', $allStatus);
-    // Expired is not reserved so might not exist.  A value of `0` won't break.
-    $expiredStatusId = array_search('Expired', $allStatus) ?: 0;
+  (civicrm_membership.membership_type_id = civicrm_membership_type.id {$activeMembershipClause})
+WHERE {$whereClause}";
 
     $query = $baseQuery . " AND civicrm_membership.is_override IS NOT NULL AND civicrm_membership.status_override_end_date IS NOT NULL";
     $dao1 = CRM_Core_DAO::executeQuery($query);
@@ -2214,18 +2236,14 @@ WHERE      civicrm_membership.is_test = 0
     }
 
     $query = $baseQuery . " AND (civicrm_membership.is_override = 0 OR civicrm_membership.is_override IS NULL)
-     AND civicrm_membership.status_id NOT IN (%1, %2, %3, %4)
+     AND {$membershipStatusClause}
      AND civicrm_membership.owner_membership_id IS NULL ";
-    $params = [
-      1 => [$pendingStatusId, 'Integer'],
-      2 => [$cancelledStatusId, 'Integer'],
-      3 => [$expiredStatusId, 'Integer'],
-      4 => [$deceaseStatusId, 'Integer'],
-    ];
-    $dao2 = CRM_Core_DAO::executeQuery($query, $params);
+
+    $allMembershipTypes = CRM_Member_BAO_MembershipType::getAllMembershipTypes();
+
+    $dao2 = CRM_Core_DAO::executeQuery($query, $queryParams);
 
     while ($dao2->fetch()) {
-      // echo ".";
       $processCount++;
 
       // Put common parameters into array for easy access
@@ -2234,7 +2252,7 @@ WHERE      civicrm_membership.is_test = 0
         'status_id' => $dao2->status_id,
         'contact_id' => $dao2->contact_id,
         'membership_type_id' => $dao2->membership_type_id,
-        'membership_type' => $allTypes[$dao2->membership_type_id],
+        'membership_type' => $allMembershipTypes[$dao2->membership_type_id]['name'],
         'join_date' => $dao2->join_date,
         'start_date' => $dao2->start_date,
         'end_date' => $dao2->end_date,
@@ -2261,7 +2279,6 @@ WHERE      civicrm_membership.is_test = 0
         $memParams = $memberParams;
         $memParams['status_id'] = $statusId;
         $memParams['createActivity'] = TRUE;
-        $memParams['version'] = 3;
 
         // Unset columns which should remain unchanged from their current saved
         // values. This avoids race condition in which these values may have
@@ -2278,7 +2295,7 @@ WHERE      civicrm_membership.is_test = 0
         //since there is change in status.
 
         //process member record.
-        civicrm_api('membership', 'create', $memParams);
+        civicrm_api3('membership', 'create', $memParams);
         $updateCount++;
       }
     }
diff --git a/api/v3/Job.php b/api/v3/Job.php
index f34d7d48a2245f1f81713596c068f6a61fc74525..e791235232659d41f17084fb41d6cd9ee782a646 100644
--- a/api/v3/Job.php
+++ b/api/v3/Job.php
@@ -466,7 +466,14 @@ function civicrm_api3_job_process_membership($params) {
     return civicrm_api3_create_error('Could not acquire lock, another Membership Processing process is running');
   }
 
-  $result = CRM_Member_BAO_Membership::updateAllMembershipStatus();
+  // We need to pass this through as a simple array of membership status IDs as values.
+  if (!empty($params['exclude_membership_status_ids'])) {
+    is_array($params['exclude_membership_status_ids']) ?: $params['exclude_membership_status_ids'] = [$params['exclude_membership_status_ids']];
+  }
+  if (!empty($params['exclude_membership_status_ids']['IN'])) {
+    $params['exclude_membership_status_ids'] = $params['exclude_membership_status_ids']['IN'];
+  }
+  $result = CRM_Member_BAO_Membership::updateAllMembershipStatus($params);
   $lock->release();
 
   if ($result['is_error'] == 0) {
@@ -477,6 +484,25 @@ function civicrm_api3_job_process_membership($params) {
   }
 }
 
+function _civicrm_api3_job_process_membership_spec(&$params) {
+  $params['exclude_test_memberships']['api.default'] = TRUE;
+  $params['exclude_test_memberships']['title'] = 'Exclude test memberships';
+  $params['exclude_test_memberships']['description'] = 'Exclude test memberships from calculations (default = TRUE)';
+  $params['exclude_test_memberships']['type'] = CRM_Utils_Type::T_BOOLEAN;
+  $params['only_active_membership_types']['api.default'] = TRUE;
+  $params['only_active_membership_types']['title'] = 'Exclude disabled membership types';
+  $params['only_active_membership_types']['description'] = 'Exclude disabled membership types from calculations (default = TRUE)';
+  $params['only_active_membership_types']['type'] = CRM_Utils_Type::T_BOOLEAN;
+  $params['exclude_membership_status_ids']['title'] = 'Exclude membership status IDs from calculations';
+  $params['exclude_membership_status_ids']['description'] = 'Default: Exclude Pending, Cancelled, Expired. Deceased will always be excluded';
+  $params['exclude_membership_status_ids']['type'] = CRM_Utils_Type::T_INT;
+  $params['exclude_membership_status_ids']['pseudoconstant'] = [
+    'table' => 'civicrm_membership_status',
+    'keyColumn' => 'id',
+    'labelColumn' => 'label',
+  ];
+}
+
 /**
  * This api checks and updates the status of all survey respondents.
  *
diff --git a/tests/phpunit/CRM/Member/BAO/MembershipTest.php b/tests/phpunit/CRM/Member/BAO/MembershipTest.php
index 8e0307e799d6365895c9bbd6e32f79e344989a5e..8cebb74c1c40edd5a21cec6f822619fa4d4c545f 100644
--- a/tests/phpunit/CRM/Member/BAO/MembershipTest.php
+++ b/tests/phpunit/CRM/Member/BAO/MembershipTest.php
@@ -550,7 +550,7 @@ class CRM_Member_BAO_MembershipTest extends CiviUnitTestCase {
 
     $createdMembership = CRM_Member_BAO_Membership::create($params);
 
-    CRM_Member_BAO_Membership::updateAllMembershipStatus();
+    civicrm_api3('Job', 'process_membership');
 
     $membershipAfterProcess = civicrm_api3('Membership', 'get', [
       'sequential' => 1,
@@ -578,7 +578,7 @@ class CRM_Member_BAO_MembershipTest extends CiviUnitTestCase {
 
     $createdMembership = CRM_Member_BAO_Membership::create($params);
 
-    CRM_Member_BAO_Membership::updateAllMembershipStatus();
+    civicrm_api3('Job', 'process_membership');
 
     $membershipAfterProcess = civicrm_api3('Membership', 'get', [
       'sequential' => 1,
@@ -605,7 +605,7 @@ class CRM_Member_BAO_MembershipTest extends CiviUnitTestCase {
 
     $createdMembership = CRM_Member_BAO_Membership::create($params);
 
-    CRM_Member_BAO_Membership::updateAllMembershipStatus();
+    civicrm_api3('Job', 'process_membership');
 
     $membershipAfterProcess = civicrm_api3('Membership', 'get', [
       'sequential' => 1,
diff --git a/tests/phpunit/api/v3/JobProcessMembershipTest.php b/tests/phpunit/api/v3/JobProcessMembershipTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e0eaccbd5415eaeff6b2f4d55ea377422e35cb12
--- /dev/null
+++ b/tests/phpunit/api/v3/JobProcessMembershipTest.php
@@ -0,0 +1,515 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC. All rights reserved.                        |
+ |                                                                    |
+ | This work is published under the GNU AGPLv3 license with some      |
+ | permitted exceptions and without any warranty. For full license    |
+ | and copyright information, see https://civicrm.org/licensing       |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ * Specific tests for the `process_membership` job.
+ *
+ * @link https://github.com/civicrm/civicrm-core/pull/16298
+ * @package CiviCRM_APIv3
+ * @subpackage API_Job
+ *
+ * @copyright CiviCRM LLC https://civicrm.org/licensing
+ */
+
+/**
+ * Class api_v3_JobProcessMembershipTest
+ * @group headless
+ */
+class api_v3_JobProcessMembershipTest extends CiviUnitTestCase {
+  protected $_apiversion = 3;
+
+  public $DBResetRequired = FALSE;
+  public $_entity = 'Job';
+
+  /**
+   * Caches membership status names in a key, value array
+   *
+   * @var array
+   */
+  public $_statuses;
+
+  /**
+   * Caches membership types in a key, value array
+   *
+   * @var array
+   */
+  public $_types;
+
+  /**
+   * Caches some reference dates
+   *
+   * @var string
+   */
+  public $_yesterday;
+
+  /**
+   * @var string
+   */
+  public $_today;
+
+  /**
+   * @var string
+   */
+  public $_tomorrow;
+
+  public function setUp() {
+    parent::setUp();
+    $this->loadReferenceDates();
+    $this->loadMembershipStatuses();
+    $this->loadMembershipTypes();
+  }
+
+  public function loadMembershipStatuses() {
+    $statuses = civicrm_api3('MembershipStatus', 'get', ['options' => ['limit' => 0]])['values'];
+    $this->_statuses = array_map(
+      function($status) {
+        return $status['name'];
+      },
+      $statuses
+    );
+  }
+
+  public function loadMembershipTypes() {
+    $this->membershipTypeCreate(['name' => 'General']);
+    $this->membershipTypeCreate(['name' => 'Old']);
+    $types = civicrm_api3('MembershipType', 'get', ['options' => ['limit' => 0]])['values'];
+    $this->_types = array_map(
+      function($type) {
+        return $type['name'];
+      },
+      $types
+    );
+  }
+
+  public function loadReferenceDates() {
+    $this->_yesterday = date('Y-m-d', time() - 60 * 60 * 24);
+    $this->_today = date('Y-m-d');
+    $this->_tomorrow = date('Y-m-d', time() + 60 * 60 * 24);
+  }
+
+  public function tearDown() {
+    parent::tearDown();
+
+    // For each case, the `old` membershipt type must start as
+    // active, so we can assign it (we'll disabled it after
+    // assigning it)
+    $this->callAPISuccess('MembershipType', 'create', [
+      'id' => array_search('Old', $this->_types),
+      'is_active' => TRUE,
+    ]);
+  }
+
+  /**
+   * Creates a membership that is expired but that should be ignored
+   * by the process as it is in `deceased` status.
+   */
+  public function createDeceasedMembershipThatShouldBeExpired() {
+    $contactId = $this->individualCreate(['is_deceased' => FALSE]);
+    $membershipId = $this->contactMembershipCreate([
+      'contact_id' => $contactId,
+      'start_date' => $this->_yesterday,
+      'end_date' => $this->_yesterday,
+    ]);
+
+    $this->callAPISuccess('Membership', 'create', [
+      'id' => $membershipId,
+      'status_id' => array_search('Deceased', $this->_statuses),
+    ]);
+
+    return $membershipId;
+  }
+
+  /**
+   * Creates a test membership in `grace` status that should be
+   * in `current` status but that won't be updated unless the process
+   * is explicitly told not to exclude tests.
+   */
+  public function createTestMembershipThatShouldBeCurrent() {
+    $contactId = $this->individualCreate();
+    $membershipId = $this->contactMembershipCreate([
+      'contact_id' => $contactId,
+      'start_date' => $this->_yesterday,
+      'end_date' => $this->_tomorrow,
+      'is_test' => TRUE,
+    ]);
+
+    $this->callAPISuccess('Membership', 'create', [
+      'id' => $membershipId,
+      'status_id' => array_search('Grace', $this->_statuses),
+    ]);
+
+    return $membershipId;
+  }
+
+  /**
+   * Creates a grace membership that should be in `current` status
+   * that should be fixed even when the process is executed with
+   * the default parameters.
+   */
+  public function createGraceMembershipThatShouldBeCurrent() {
+    $contactId = $this->individualCreate();
+    $membershipId = $this->contactMembershipCreate([
+      'contact_id' => $contactId,
+      'start_date' => $this->_yesterday,
+      'end_date' => $this->_tomorrow,
+    ]);
+
+    $this->callAPISuccess('Membership', 'create', [
+      'id' => $membershipId,
+      'status_id' => array_search('Grace', $this->_statuses),
+    ]);
+
+    return $membershipId;
+  }
+
+  /**
+   * Creates a pending membership that should be in `current` status
+   * that won't be fixed unless the process is executed
+   * with an explicit `exclude_membership_status_ids` list that
+   * doesn't include it.
+   */
+  public function createPendingMembershipThatShouldBeCurrent() {
+    $contactId = $this->individualCreate();
+    $membershipId = $this->contactMembershipCreate([
+      'contact_id' => $contactId,
+      'start_date' => $this->_yesterday,
+      'end_date' => $this->_tomorrow,
+    ]);
+
+    $this->callAPISuccess('Membership', 'create', [
+      'id' => $membershipId,
+      'status_id' => array_search('Pending', $this->_statuses),
+    ]);
+
+    return $membershipId;
+  }
+
+  /**
+   * Creates a membership that uses an inactive membership type
+   * and should be in `current` status.
+   */
+  public function createOldMembershipThatShouldBeCurrent() {
+    $contactId = $this->individualCreate();
+    $membershipId = $this->contactMembershipCreate([
+      'contact_id' => $contactId,
+      'start_date' => $this->_yesterday,
+      'end_date' => $this->_tomorrow,
+      'membership_type_id' => array_search('Old', $this->_types),
+    ]);
+
+    $this->callAPISuccess('Membership', 'create', [
+      'id' => $membershipId,
+      'status_id' => array_search('Grace', $this->_statuses),
+    ]);
+
+    $this->callAPISuccess('MembershipType', 'create', [
+      'id' => array_search('Old', $this->_types),
+      'is_active' => FALSE,
+    ]);
+
+    return $membershipId;
+  }
+
+  /**
+   * Returns the name of the status of a membership given its id.
+   */
+  public function getMembershipStatus($membershipId) {
+    $membership = $this->callAPISuccess('Membership', 'getsingle', ['id' => $membershipId]);
+    $statusId = $membership['status_id'];
+    return $this->_statuses[$statusId];
+  }
+
+  /**
+   * Test that by default test memberships are excluded.
+   */
+  public function testByDefaultTestsAreExcluded() {
+    $testId = $this->createTestMembershipThatShouldBeCurrent();
+
+    $this->callAPISuccess('job', 'process_membership', []);
+
+    $this->assertEquals('Grace', $this->getMembershipStatus($testId));
+  }
+
+  /**
+   * Test that by default memberships of inactive types are excluded.
+   */
+  public function testByDefaultInactiveAreExcluded() {
+    $oldId = $this->createOldMembershipThatShouldBeCurrent();
+
+    $this->callAPISuccess('job', 'process_membership', []);
+
+    $this->assertEquals('Grace', $this->getMembershipStatus($oldId));
+  }
+
+  /**
+   * Test that by default grace memberships are considered.
+   */
+  public function testByDefaultGraceIsConsidered() {
+    $graceId = $this->createGraceMembershipThatShouldBeCurrent();
+
+    $this->callAPISuccess('job', 'process_membership', []);
+
+    $this->assertEquals('Current', $this->getMembershipStatus($graceId));
+  }
+
+  /**
+   * Test that by default pending memberships are excluded.
+   *
+   * The pending status is still excluded as it's in the
+   * exclude_membership_status_ids list by default.
+   */
+  public function testByDefaultPendingIsExcluded() {
+    $pendingId = $this->createPendingMembershipThatShouldBeCurrent();
+
+    $this->callAPISuccess('job', 'process_membership', []);
+
+    $this->assertEquals('Pending', $this->getMembershipStatus($pendingId));
+  }
+
+  /**
+   * Test that by default memberships of type deceased are excluded.
+   */
+  public function testByDefaultDeceasedIsExcluded() {
+    $deceasedId = $this->createDeceasedMembershipThatShouldBeExpired();
+
+    $this->callAPISuccess('job', 'process_membership', []);
+
+    $this->assertEquals('Deceased', $this->getMembershipStatus($deceasedId));
+  }
+
+  /**
+   * Test that when including test memberships,
+   * pending memberships are excluded.
+   *
+   * The pending status is still excluded as it's in the
+   * exclude_membership_status_ids list by default.
+   */
+  public function testIncludingTestMembershipsExcludesPending() {
+    $pendingId = $this->createPendingMembershipThatShouldBeCurrent();
+
+    $this->callAPISuccess('job', 'process_membership', [
+      'exclude_test_memberships' => FALSE,
+    ]);
+
+    $this->assertEquals('Pending', $this->getMembershipStatus($pendingId));
+  }
+
+  /**
+   * Test that when including test memberships,
+   * grace memberships are considered.
+   */
+  public function testIncludingTestMembershipsConsidersGrace() {
+    $graceId = $this->createGraceMembershipThatShouldBeCurrent();
+
+    $this->callAPISuccess('job', 'process_membership', [
+      'exclude_test_memberships' => FALSE,
+    ]);
+
+    $this->assertEquals('Current', $this->getMembershipStatus($graceId));
+  }
+
+  /**
+   * Test that when including test memberships,
+   * memberships of inactive types are still ignored.
+   */
+  public function testIncludingTestMembershipsIgnoresInactive() {
+    $oldId = $this->createOldMembershipThatShouldBeCurrent();
+
+    $this->callAPISuccess('job', 'process_membership', [
+      'exclude_test_memberships' => FALSE,
+    ]);
+
+    $this->assertEquals('Grace', $this->getMembershipStatus($oldId));
+  }
+
+  /**
+   * Test that when including test memberships,
+   * acually includes test memberships.
+   */
+  public function testIncludingTestMembershipsActuallyIncludesThem() {
+    $testId = $this->createTestMembershipThatShouldBeCurrent();
+
+    $this->callAPISuccess('job', 'process_membership', [
+      'exclude_test_memberships' => FALSE,
+    ]);
+
+    $this->assertEquals('Current', $this->getMembershipStatus($testId));
+  }
+
+  /**
+   * Test that when including test memberships,
+   * memberships of type deceased are still ignored.
+   */
+  public function testIncludingTestMembershipsStillIgnoresDeceased() {
+    $deceasedId = $this->createDeceasedMembershipThatShouldBeExpired();
+
+    $this->callAPISuccess('job', 'process_membership', [
+      'exclude_test_memberships' => FALSE,
+    ]);
+
+    $this->assertEquals('Deceased', $this->getMembershipStatus($deceasedId));
+  }
+
+  /**
+   * Test that when including inactive membership types,
+   * pending memberships are considered.
+   *
+   * The pending status is still excluded as it's in the
+   * exclude_membership_status_ids list by default.
+   */
+  public function testIncludingInactiveMembershipTypesStillExcludesPending() {
+    $pendingId = $this->createPendingMembershipThatShouldBeCurrent();
+
+    $this->callAPISuccess('job', 'process_membership', [
+      'only_active_membership_types' => FALSE,
+    ]);
+
+    $this->assertEquals('Pending', $this->getMembershipStatus($pendingId));
+  }
+
+  /**
+   * Test that when including inactive membership types,
+   * grace memberships are considered.
+   */
+  public function testIncludingInactiveMembershipTypesConsidersGrace() {
+    $graceId = $this->createGraceMembershipThatShouldBeCurrent();
+
+    $this->callAPISuccess('job', 'process_membership', [
+      'only_active_membership_types' => FALSE,
+    ]);
+
+    $this->assertEquals('Current', $this->getMembershipStatus($graceId));
+  }
+
+  /**
+   * Test that when including inactive membership types,
+   * memberships of disabled membership types are considered.
+   */
+  public function testIncludingInactiveMembershipTypesConsidersInactive() {
+    $oldId = $this->createOldMembershipThatShouldBeCurrent();
+
+    $this->callAPISuccess('job', 'process_membership', [
+      'only_active_membership_types' => FALSE,
+    ]);
+
+    $this->assertEquals('Current', $this->getMembershipStatus($oldId));
+  }
+
+  /**
+   * Test that when including inactive membership types,
+   * test memberships are still ignored.
+   */
+  public function testIncludingInactiveMembershipTypesStillIgnoresTests() {
+    $testId = $this->createTestMembershipThatShouldBeCurrent();
+
+    $this->callAPISuccess('job', 'process_membership', [
+      'only_active_membership_types' => FALSE,
+    ]);
+
+    $this->assertEquals('Grace', $this->getMembershipStatus($testId));
+  }
+
+  /**
+   * Test that when including inactive membership types,
+   * memberships of type deceased are still ignored.
+   */
+  public function testMembershipTypeDeceasedIsExcluded() {
+    $deceasedId = $this->createDeceasedMembershipThatShouldBeExpired();
+
+    $this->callAPISuccess('job', 'process_membership', [
+      'only_active_membership_types' => FALSE,
+    ]);
+
+    $this->assertEquals('Deceased', $this->getMembershipStatus($deceasedId));
+  }
+
+  /**
+   * Test that when explicitly setting the status ids to exclude,
+   * memberships in deceased status are still ignored.
+   */
+  public function testSpecifyingTheStatusIdsToExcludeStillExcludesDeceased() {
+    $deceasedId = $this->createDeceasedMembershipThatShouldBeExpired();
+
+    $this->callAPISuccess('job', 'process_membership', [
+      'exclude_membership_status_ids' => [
+        array_search('Cancelled', $this->_statuses),
+      ],
+    ]);
+
+    $this->assertEquals('Deceased', $this->getMembershipStatus($deceasedId));
+  }
+
+  /**
+   * Test that when explicitly setting the status ids to exclude,
+   * test memberships are still ignored.
+   */
+  public function testSpecifyingTheStatusIdsToExcludeStillExcludesTests() {
+    $testId = $this->createTestMembershipThatShouldBeCurrent();
+
+    $this->callAPISuccess('job', 'process_membership', [
+      'exclude_membership_status_ids' => [
+        array_search('Cancelled', $this->_statuses),
+      ],
+    ]);
+
+    $this->assertEquals('Grace', $this->getMembershipStatus($testId));
+  }
+
+  /**
+   * Test that when explicitly setting the status ids to exclude,
+   * memberships of disabled membership types are still ignored.
+   */
+  public function testSpecifyingTheStatusIdsToExcludeStillExcludesInactive() {
+    $oldId = $this->createOldMembershipThatShouldBeCurrent();
+
+    $this->callAPISuccess('job', 'process_membership', [
+      'exclude_membership_status_ids' => [
+        array_search('Cancelled', $this->_statuses),
+      ],
+    ]);
+
+    $this->assertEquals('Grace', $this->getMembershipStatus($oldId));
+  }
+
+  /**
+   * Test that when explicitly setting the status ids to exclude,
+   * grace memberships are considered by default.
+   */
+  public function testSpecifyingTheStatusIdsToExcludeGraceIsIncludedByDefault() {
+    $graceId = $this->createGraceMembershipThatShouldBeCurrent();
+
+    $this->callAPISuccess('job', 'process_membership', [
+      'exclude_membership_status_ids' => [
+        array_search('Cancelled', $this->_statuses),
+      ],
+    ]);
+
+    $this->assertEquals('Current', $this->getMembershipStatus($graceId));
+  }
+
+  /**
+   * Test that when explicitly setting the status ids to exclude,
+   * if the specified list doesn't include pending, then pending
+   * memberships are considered.
+   */
+  public function testSpecifyingTheStatusIdsToExcludePendingIsExcludedByDefault() {
+    $pendingId = $this->createPendingMembershipThatShouldBeCurrent();
+
+    $this->callAPISuccess('job', 'process_membership', [
+      'exclude_membership_status_ids' => [
+        array_search('Cancelled', $this->_statuses),
+      ],
+    ]);
+
+    $this->assertEquals('Current', $this->getMembershipStatus($pendingId));
+  }
+
+}