diff --git a/CRM/Core/Payment/PayPalProIPN.php b/CRM/Core/Payment/PayPalProIPN.php
index 4dc98b5c31585e46259c502cdfd7397bba850247..6f9fdeb59b827aced6e32caf241c213067ef02f9 100644
--- a/CRM/Core/Payment/PayPalProIPN.php
+++ b/CRM/Core/Payment/PayPalProIPN.php
@@ -291,7 +291,7 @@ class CRM_Core_Payment_PayPalProIPN extends CRM_Core_Payment_BaseIPN {
 
           // In future moving to create pending & then complete, but this OK for now.
           // Also consider accepting 'Failed' like other processors.
-          $input['contribution_status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_ContributionRecur', 'contribution_status_id', 'Completed');
+          $input['contribution_status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed');
           $input['invoice_id'] = md5(uniqid(rand(), TRUE));
           $input['original_contribution_id'] = $this->getContributionID();
           $input['contribution_recur_id'] = $this->getContributionRecurID();
diff --git a/tests/phpunit/CRM/Core/Payment/PayPalProIPNTest.php b/tests/phpunit/CRM/Core/Payment/PayPalProIPNTest.php
index fd763db77cdf9b9fc7cbead251628d67402f87fa..af7ee37ced486e68f1ae6e6b764ed51e11e8d18a 100644
--- a/tests/phpunit/CRM/Core/Payment/PayPalProIPNTest.php
+++ b/tests/phpunit/CRM/Core/Payment/PayPalProIPNTest.php
@@ -9,6 +9,8 @@
  +--------------------------------------------------------------------+
  */
 
+use Civi\Api4\Contribution;
+
 /**
  * Class CRM_Core_Payment_PayPalProIPNTest
  * @group headless
@@ -36,7 +38,7 @@ class CRM_Core_Payment_PayPalProIPNTest extends CiviUnitTestCase {
     $this->_paymentProcessorID = $this->paymentProcessorCreate(['is_test' => 0]);
     $this->_contactID = $this->individualCreate();
     $contributionPage = $this->callAPISuccess('contribution_page', 'create', [
-      'title' => "Test Contribution Page",
+      'title' => 'Test Contribution Page',
       'financial_type_id' => $this->_financialTypeID,
       'currency' => 'USD',
       'payment_processor' => $this->_paymentProcessorID,
@@ -53,32 +55,38 @@ class CRM_Core_Payment_PayPalProIPNTest extends CiviUnitTestCase {
   }
 
   /**
-   * Test IPN response updates contribution_recur & contribution for first & second contribution.
+   * Test IPN response updates contribution_recur & contribution for first &
+   * second contribution.
    *
-   * The scenario is that a pending contribution exists and the first call will update it to completed.
-   * The second will create a new contribution.
+   * The scenario is that a pending contribution exists and the first call will
+   * update it to completed. The second will create a new contribution.
+   *
+   * @throws \CRM_Core_Exception
    */
-  public function testIPNPaymentRecurSuccess() {
+  public function testIPNPaymentRecurSuccess(): void {
+    $this->disorganizeOptionValues();
     $this->setupRecurringPaymentProcessorTransaction();
     global $_GET;
     $_GET = $this->getPaypalProRecurTransaction();
     $paypalIPN = new CRM_Core_Payment_PayPalProIPN($this->getPaypalProRecurTransaction());
     $paypalIPN->main();
-    $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $this->_contributionID]);
-    $this->assertEquals(1, $contribution['contribution_status_id']);
+    $contribution = Contribution::get()->addWhere('id', '=', $this->_contributionID)
+      ->addSelect('contribution_status_id:name', 'trxn_id', 'source')
+      ->execute()->first();
+    $this->assertEquals('Completed', $contribution['contribution_status_id:name']);
     $this->assertEquals('8XA571746W2698126', $contribution['trxn_id']);
     // source gets set by processor
-    $this->assertTrue(substr($contribution['contribution_source'], 0, 20) == "Online Contribution:");
+    $this->assertEquals('Online Contribution:', substr($contribution['source'], 0, 20));
     $contributionRecur = $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $this->_contributionRecurID]);
     $this->assertEquals(5, $contributionRecur['contribution_status_id']);
     $paypalIPN = new CRM_Core_Payment_PayPalProIPN($this->getPaypalProRecurSubsequentTransaction());
     $paypalIPN->main();
-    $contribution = $this->callAPISuccess('contribution', 'get', [
+    $contribution = $this->callAPISuccess('Contribution', 'get', [
       'contribution_recur_id' => $this->_contributionRecurID,
       'sequential' => 1,
     ]);
     $this->assertEquals(2, $contribution['count']);
-    $this->assertEquals('secondone', $contribution['values'][1]['trxn_id']);
+    $this->assertEquals('second-one', $contribution['values'][1]['trxn_id']);
     $this->assertEquals('Debit Card', $contribution['values'][1]['payment_instrument']);
   }
 
@@ -87,7 +95,7 @@ class CRM_Core_Payment_PayPalProIPNTest extends CiviUnitTestCase {
    *
    * @throws \CRM_Core_Exception
    */
-  public function testIPNPaymentMembershipRecurSuccess() {
+  public function testIPNPaymentMembershipRecurSuccess(): void {
     $durationUnit = 'year';
     $this->setupMembershipRecurringPaymentProcessorTransaction(['duration_unit' => $durationUnit, 'frequency_unit' => $durationUnit]);
     $this->callAPISuccessGetSingle('membership_payment', []);
@@ -98,7 +106,7 @@ class CRM_Core_Payment_PayPalProIPNTest extends CiviUnitTestCase {
     $this->assertEquals(1, $contribution['contribution_status_id']);
     $this->assertEquals('8XA571746W2698126', $contribution['trxn_id']);
     // source gets set by processor
-    $this->assertTrue(substr($contribution['contribution_source'], 0, 20) == "Online Contribution:");
+    $this->assertEquals('Online Contribution:', substr($contribution['contribution_source'], 0, 20));
     $contributionRecur = $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $this->_contributionRecurID]);
     $this->assertEquals(5, $contributionRecur['contribution_status_id']);
     $paypalIPN = new CRM_Core_Payment_PaypalProIPN($this->getPaypalProRecurSubsequentTransaction());
@@ -111,7 +119,7 @@ class CRM_Core_Payment_PayPalProIPNTest extends CiviUnitTestCase {
       'sequential' => 1,
     ]);
     $this->assertEquals(2, $contribution['count']);
-    $this->assertEquals('secondone', $contribution['values'][1]['trxn_id']);
+    $this->assertEquals('second-one', $contribution['values'][1]['trxn_id']);
     $this->callAPISuccessGetCount('line_item', [
       'entity_id' => $this->ids['membership'],
       'entity_table' => 'civicrm_membership',
@@ -155,7 +163,7 @@ class CRM_Core_Payment_PayPalProIPNTest extends CiviUnitTestCase {
       'sequential' => 1,
     ]);
     $this->assertEquals(1, $contribution['count']);
-    $this->assertEquals('secondone', $contribution['values'][0]['trxn_id']);
+    $this->assertEquals('second-one', $contribution['values'][0]['trxn_id']);
     $this->assertEquals(strtotime('03:59:05 Jul 14, 2013 PDT'), strtotime($contribution['values'][0]['receive_date']));
   }
 
@@ -348,8 +356,8 @@ class CRM_Core_Payment_PayPalProIPNTest extends CiviUnitTestCase {
    *
    * @return array
    */
-  public function getPaypalProRecurSubsequentTransaction() {
-    return array_merge($this->getPaypalProRecurTransaction(), ['txn_id' => 'secondone']);
+  public function getPaypalProRecurSubsequentTransaction(): array {
+    return array_merge($this->getPaypalProRecurTransaction(), ['txn_id' => 'second-one']);
   }
 
   /**
diff --git a/tests/phpunit/CiviTest/CiviUnitTestCase.php b/tests/phpunit/CiviTest/CiviUnitTestCase.php
index b9a4a5ffa674023d85a0f8192ae3af432f7c79e0..a6986d28f24b4526eec4d9dcde0b53e50b1929c1 100644
--- a/tests/phpunit/CiviTest/CiviUnitTestCase.php
+++ b/tests/phpunit/CiviTest/CiviUnitTestCase.php
@@ -285,8 +285,7 @@ class CiviUnitTestCase extends PHPUnit\Framework\TestCase {
   protected function setUp(): void {
     CRM_Core_I18n::clearLocale();
     parent::setUp();
-    $session = CRM_Core_Session::singleton();
-    $session->set('userID', NULL);
+    CRM_Core_Session::singleton()->set('userID');
 
     $this->_apiversion = 3;
 
@@ -311,7 +310,6 @@ class CiviUnitTestCase extends PHPUnit\Framework\TestCase {
 
     // disable any left-over test extensions
     CRM_Core_DAO::executeQuery('DELETE FROM civicrm_extension WHERE full_name LIKE "test.%"');
-
     // reset all the caches
     CRM_Utils_System::flushCache();
 
@@ -457,10 +455,7 @@ class CiviUnitTestCase extends PHPUnit\Framework\TestCase {
     $this->resetLabels();
 
     error_reporting(E_ALL & ~E_NOTICE);
-    CRM_Utils_Hook::singleton()->reset();
-    if ($this->hookClass) {
-      $this->hookClass->reset();
-    }
+    $this->resetHooks();
     CRM_Core_Session::singleton()->reset(1);
 
     if ($this->tx) {
@@ -988,11 +983,10 @@ class CiviUnitTestCase extends PHPUnit\Framework\TestCase {
       'payment_instrument_id' => 1,
       'non_deductible_amount' => 10.00,
       'source' => 'SSF',
-      'contribution_status_id' => 1,
+      'contribution_status_id' => 'Completed',
     ], $params);
 
-    $result = $this->callAPISuccess('contribution', 'create', $params);
-    return $result['id'];
+    return $this->callAPISuccess('Contribution', 'create', $params)['id'];
   }
 
   /**
@@ -1798,6 +1792,8 @@ class CiviUnitTestCase extends PHPUnit\Framework\TestCase {
 
   /**
    * Clean up financial entities after financial tests (so we remember to get all the tables :-))
+   *
+   * @noinspection PhpUnhandledExceptionInspection
    */
   public function quickCleanUpFinancialEntities(): void {
     $tablesToTruncate = [
@@ -1852,6 +1848,7 @@ class CiviUnitTestCase extends PHPUnit\Framework\TestCase {
     catch (CRM_Core_Exception $e) {
       $this->fail('failed to cleanup financial types ' . $e->getMessage());
     }
+    $this->organizeOptionValues();
     CRM_Core_PseudoConstant::flush('taxRates');
     System::singleton()->flushProcessors();
     // @fixme this parameter is leaking - it should not be defined as a class static
@@ -3926,4 +3923,44 @@ WHERE table_schema = DATABASE()");
     }
   }
 
+  /**
+   * Disorganize our option values to ensure we are not relying on luck.
+   *
+   * For example our contributions and recurring contributions use different
+   * option groups for contribution_status_id but by happy co-incidence (ahem)
+   * both have 1 for completed by default. This upsets that co-incidence for more
+   * robust testing. Ideally we would do this for all tests & for a range of values
+   * but there is too much hard-coding to roll that out right now.
+   *
+   * @noinspection PhpUnhandledExceptionInspection
+   */
+  protected function disorganizeOptionValues(): void {
+    OptionValue::update(FALSE)->setValues(['value' => 20])
+      ->addWhere('name', '=', 'Completed')
+      ->addWhere('option_group_id:name', '=', 'contribution_status')
+      ->execute();
+  }
+
+  /**
+   * This undoes the `disorganizeOptionValues` function.
+   *
+   * @noinspection PhpUnhandledExceptionInspection
+   */
+  protected function organizeOptionValues(): void {
+    OptionValue::update(FALSE)->setValues(['value' => 1])
+      ->addWhere('name', '=', 'Completed')
+      ->addWhere('option_group_id:name', '=', 'contribution_status')
+      ->execute();
+  }
+
+  /**
+   * Reset any registered hooks.
+   */
+  protected function resetHooks(): void {
+    CRM_Utils_Hook::singleton()->reset();
+    if ($this->hookClass) {
+      $this->hookClass->reset();
+    }
+  }
+
 }
diff --git a/tests/phpunit/api/v3/ContributionTest.php b/tests/phpunit/api/v3/ContributionTest.php
index f41051ef5f1dd0cdd88fdbd59e752f6aa2b6d067..ce043a42c81f5e22a2557e4e47d6b6840903eaa8 100644
--- a/tests/phpunit/api/v3/ContributionTest.php
+++ b/tests/phpunit/api/v3/ContributionTest.php
@@ -90,7 +90,7 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
       'fee_amount' => 5.00,
       'net_amount' => 95.00,
       'source' => 'SSF',
-      'contribution_status_id' => 1,
+      'contribution_status_id' => 'Completed',
     ];
     $this->_processorParams = [
       'domain_id' => 1,
diff --git a/tests/phpunit/api/v3/JobTest.php b/tests/phpunit/api/v3/JobTest.php
index 237a2bad761a878c3c6cbc9d7a364ed2b207e733..72292b6a6e964e4bb4defb5460faf7c928bbbdf2 100644
--- a/tests/phpunit/api/v3/JobTest.php
+++ b/tests/phpunit/api/v3/JobTest.php
@@ -57,6 +57,7 @@ class api_v3_JobTest extends CiviUnitTestCase {
    * Cleanup after test.
    */
   public function tearDown(): void {
+    $this->resetHooks();
     $this->quickCleanUpFinancialEntities();
     $this->quickCleanup(['civicrm_contact', 'civicrm_address', 'civicrm_email', 'civicrm_website', 'civicrm_phone', 'civicrm_job', 'civicrm_action_log', 'civicrm_action_schedule', 'civicrm_group', 'civicrm_group_contact'], TRUE);
     $this->quickCleanup(['civicrm_contact', 'civicrm_address', 'civicrm_email', 'civicrm_website', 'civicrm_phone'], TRUE);
@@ -124,6 +125,7 @@ class api_v3_JobTest extends CiviUnitTestCase {
    * e.g {if {contact.first_name|boolean}
    *
    * @dataProvider dataProviderNamesAndGreetings
+   * @throws \CRM_Core_Exception
    */
   public function testUpdateGreetingBooleanToken($params, $expectedEmailGreeting): void {
     $this->setEmailGreetingTemplateToConditional();
@@ -287,7 +289,7 @@ class api_v3_JobTest extends CiviUnitTestCase {
       'return' => 'id',
       'name_a_b' => 'Employee of',
     ]);
-    $result = $this->callAPISuccess('relationship', 'create', [
+    $result = $this->callAPISuccess('Relationship', 'create', [
       'relationship_type_id' => $relationshipTypeID,
       'contact_id_a' => $individualID,
       'contact_id_b' => $orgID,
@@ -451,8 +453,6 @@ class api_v3_JobTest extends CiviUnitTestCase {
    * Check that the merge carries across various related entities.
    *
    * Note the group combinations & expected results:
-   *
-   * @throws \CRM_Core_Exception
    */
   public function testBatchMergeWithAssets(): void {
     $contactID = $this->individualCreate();