Merge pull request #14812 from eileenmcnaughton/ee
[civicrm-core.git] / tests / phpunit / CRM / Export / BAO / ExportTest.php
index a6107e464dcb99e2ce110fc0597944582bc99da1..29756b17c51c9e144300acc32b6ec25eaf07eecc 100644 (file)
@@ -2,6 +2,7 @@
 
 /**
  * Class CRM_Core_DAOTest
+ *
  * @group headless
  */
 class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
@@ -27,7 +28,6 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
    */
   protected $activityIDs = [];
 
-
   /**
    * Contribution IDs created for testing.
    *
@@ -44,10 +44,33 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
 
   protected $locationTypes = [];
 
+  /**
+   * Processor generated in test.
+   *
+   * @var \CRM_Export_BAO_ExportProcessor
+   */
+  protected $processor;
+
+  /**
+   * Csv output from export.
+   *
+   * @var \League\Csv\Reader
+   */
+  protected $csv;
+
+  /**
+   * Cleanup data.
+   *
+   * @throws \Exception
+   */
   public function tearDown() {
+    $this->quickCleanUpFinancialEntities();
     $this->quickCleanup([
       'civicrm_contact',
       'civicrm_email',
+      'civicrm_phone',
+      'civicrm_im',
+      'civicrm_website',
       'civicrm_address',
       'civicrm_relationship',
       'civicrm_membership',
@@ -55,114 +78,220 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
       'civicrm_case_contact',
       'civicrm_case_activity',
     ]);
-    $this->quickCleanUpFinancialEntities();
+
     if (!empty($this->locationTypes)) {
       $this->callAPISuccess('LocationType', 'delete', ['id' => $this->locationTypes['Whare Kai']['id']]);
       $this->callAPISuccess('LocationType', 'create', ['id' => $this->locationTypes['Main']['id'], 'name' => 'Main']);
     }
+    if ($this->processor && $this->processor->getTemporaryTable()) {
+      // delete the export temp table
+      CRM_Core_DAO::executeQuery("DROP TABLE IF EXISTS " . $this->processor->getTemporaryTable());
+    }
     parent::tearDown();
   }
 
   /**
    * Basic test to ensure the exportComponents function completes without error.
+   *
+   * @throws \CRM_Core_Exception
+   * @throws \League\Csv\Exception
    */
   public function testExportComponentsNull() {
-    list($tableName) = CRM_Export_BAO_Export::exportComponents(
-      TRUE,
-      array(),
-      array(),
-      NULL,
-      NULL,
-      NULL,
-      CRM_Export_Form_Select::CONTACT_EXPORT,
-      NULL,
-      NULL,
-      FALSE,
-      FALSE,
-      array(
-        'exportOption' => 1,
-        'suppress_csv_for_testing' => TRUE,
-      )
-    );
-
-    // delete the export temp table and component table
-    $sql = "DROP TABLE IF EXISTS {$tableName}";
-    CRM_Core_DAO::executeQuery($sql);
+    $this->doExportTest([]);
   }
 
   /**
    * Basic test to ensure the exportComponents function can export selected fields for contribution.
+   *
+   * @throws \CRM_Core_Exception
+   * @throws \League\Csv\Exception
    */
   public function testExportComponentsContribution() {
     $this->setUpContributionExportData();
-    $selectedFields = array(
-      array('Individual', 'first_name'),
-      array('Individual', 'last_name'),
-      array('Contribution', 'receive_date'),
-      array('Contribution', 'contribution_source'),
-      array('Individual', 'street_address', 1),
-      array('Individual', 'city', 1),
-      array('Individual', 'country', 1),
-      array('Individual', 'email', 1),
-      array('Contribution', 'trxn_id'),
-    );
+    $selectedFields = [
+      ['contact_type' => 'Individual', 'name' => 'first_name'],
+      ['contact_type' => 'Individual', 'name' => 'last_name'],
+      ['name' => 'receive_date'],
+      ['name' => 'contribution_source'],
+      ['contact_type' => 'Individual', 'name' => 'street_address', 1],
+      ['contact_type' => 'Individual', 'name' => 'city', 1],
+      ['contact_type' => 'Individual', 'name' => 'country', 1],
+      ['contact_type' => 'Individual', 'name' => 'email', 1],
+      ['name' => 'trxn_id'],
+    ];
 
-    list($tableName) = CRM_Export_BAO_Export::exportComponents(
-      TRUE,
-      $this->contributionIDs,
-      array(),
-      'receive_date desc',
-      $selectedFields,
-      NULL,
-      CRM_Export_Form_Select::CONTRIBUTE_EXPORT,
-      'civicrm_contribution.id IN ( ' . implode(',', $this->contributionIDs) . ')',
-      NULL,
-      FALSE,
-      FALSE,
-      array(
-        'exportOption' => CRM_Export_Form_Select::CONTRIBUTE_EXPORT,
-        'suppress_csv_for_testing' => TRUE,
-      )
-    );
+    $this->doExportTest([
+      'ids' => $this->contributionIDs,
+      'order' => 'receive_date desc',
+      'fields' => $selectedFields,
+      'exportMode' => CRM_Export_Form_Select::CONTRIBUTE_EXPORT,
+      'componentClause' => 'civicrm_contribution.id IN ( ' . implode(',', $this->contributionIDs) . ')',
+    ]);
+  }
+
+  /**
+   * Basic test to ensure the exportComponents function can export with soft credits enabled.
+   *
+   * @throws \CRM_Core_Exception
+   * @throws \League\Csv\Exception
+   */
+  public function testExportComponentsContributionSoftCredits() {
+    $this->setUpContributionExportData();
+    $this->callAPISuccess('ContributionSoft', 'create', ['contact_id' => $this->contactIDs[1], 'contribution_id' => $this->contributionIDs[0], 'amount' => 5]);
+    $params = [
+      ['receive_date_low', '=', '20160101000000', 0, 0],
+      ['receive_date_high', '=', '20191231235959', 0, 0],
+      ['contribution_amount_low', '=', '1', 0, 0],
+      ['contribution_amount_high', '=', '10000000', 0, 0],
+      ['contribution_test', '=', '0', 0, 0],
+      ['contribution_or_softcredits', '=', 'both', 0, 0],
+    ];
+
+    $this->doExportTest([
+      'selectAll' => FALSE,
+      'ids' => $this->contributionIDs,
+      'params' => $params,
+      'order' => 'receive_date desc',
+      'exportMode' => CRM_Export_Form_Select::CONTRIBUTE_EXPORT,
+      'componentClause' => 'civicrm_contribution.id IN ( ' . implode(',', $this->contributionIDs) . ')',
+    ]);
+
+    $this->assertEquals(array_merge($this->getBasicHeaderDefinition(FALSE), $this->getContributeHeaderDefinition()), $this->csv->getHeader());
+    $this->assertCount(3, $this->csv);
+    $row = $this->csv->fetchOne(0);
+    $this->assertEquals(95, $row['Net Amount']);
+    $this->assertEquals('', $row['Soft Credit Amount']);
+    $row = $this->csv->fetchOne(1);
+    $this->assertEquals(95, $row['Net Amount']);
+    $this->assertEquals(5, $row['Soft Credit Amount']);
+    $this->assertEquals('Anderson, Anthony', $row['Soft Credit For']);
+    $this->assertEquals($this->contributionIDs[0], $row['Soft Credit For Contribution ID']);
+
+    // Ideally we would use a randomised temp table name & use generic temp cleanup for cleanup - but
+    // for now just make sure we don't leave a mess.
+    CRM_Core_DAO::executeQuery('DROP TABLE IF EXISTS contribution_search_scredit_combined');
 
-    // delete the export temp table and component table
-    $sql = "DROP TABLE IF EXISTS {$tableName}";
-    CRM_Core_DAO::executeQuery($sql);
   }
 
   /**
    * Basic test to ensure the exportComponents function can export selected fields for contribution.
+   *
+   * @throws \CRM_Core_Exception
+   * @throws \League\Csv\Exception
    */
   public function testExportComponentsMembership() {
     $this->setUpMembershipExportData();
-    list($tableName) = CRM_Export_BAO_Export::exportComponents(
-      TRUE,
-      $this->membershipIDs,
-      [],
-      NULL,
-      NULL,
-      NULL,
-      CRM_Export_Form_Select::MEMBER_EXPORT,
-      'civicrm_membership.id IN ( ' . implode(',', $this->membershipIDs) . ')',
-      NULL,
-      FALSE,
-      FALSE,
-      array(
-        'exportOption' => CRM_Export_Form_Select::MEMBER_EXPORT,
-        'suppress_csv_for_testing' => TRUE,
-      )
-    );
-
-    $dao = CRM_Core_DAO::executeQuery('SELECT * from ' . $tableName);
-    $dao->fetch();
-    $this->assertEquals('100.00', $dao->componentpaymentfield_total_amount);
-    $this->assertEquals('Completed', $dao->componentpaymentfield_contribution_status);
-    $this->assertEquals('Credit Card', $dao->componentpaymentfield_payment_instrument);
-    $this->assertEquals(1, $dao->N);
+    $this->doExportTest([
+      'selectAll' => TRUE,
+      'ids'  => $this->membershipIDs,
+      'exportMode' => CRM_Export_Form_Select::MEMBER_EXPORT,
+      'componentClause' => 'civicrm_membership.id IN ( ' . implode(',', $this->membershipIDs) . ')',
+    ]);
 
-    // delete the export temp table and component table
-    $sql = "DROP TABLE IF EXISTS {$tableName}";
-    CRM_Core_DAO::executeQuery($sql);
+    $row = $this->csv->fetchOne();
+    $expected = [
+      'Contact ID' => $this->contactIDs[0],
+      'Contact Type' => 'Individual',
+      'Contact Subtype' => '',
+      'Do Not Email' => '',
+      'Do Not Phone' => '',
+      'Do Not Mail' => '',
+      'Do Not Sms' => '',
+      'Do Not Trade' => '',
+      'No Bulk Emails (User Opt Out)' => '',
+      'Legal Identifier' => '',
+      'External Identifier' => '',
+      'Sort Name' => 'Anderson, Anthony',
+      'Display Name' => 'Mr. Anthony Anderson II',
+      'Nickname' => '',
+      'Legal Name' => '',
+      'Image Url' => '',
+      'Preferred Communication Method' => '',
+      'Preferred Language' => 'en_US',
+      'Preferred Mail Format' => 'Both',
+      'Contact Hash' => '059023a02d27d4e7f285a40ee0e30be8',
+      'Contact Source' => '',
+      'First Name' => 'Anthony',
+      'Middle Name' => 'J.',
+      'Last Name' => 'Anderson',
+      'Individual Prefix' => 'Mr.',
+      'Individual Suffix' => 'II',
+      'Formal Title' => '',
+      'Communication Style' => 'Formal',
+      'Email Greeting ID' => '1',
+      'Postal Greeting ID' => '1',
+      'Addressee ID' => '1',
+      'Job Title' => '',
+      'Gender' => 'Female',
+      'Birth Date' => '',
+      'Deceased' => '',
+      'Deceased Date' => '',
+      'Household Name' => '',
+      'Organization Name' => '',
+      'Sic Code' => '',
+      'Unique ID (OpenID)' => '',
+      'Current Employer ID' => '',
+      'Contact is in Trash' => '',
+      'Created Date' => '2019-07-11 09:56:18',
+      'Modified Date' => '2019-07-11 09:56:19',
+      'Addressee' => 'Mr. Anthony J. Anderson II',
+      'Email Greeting' => 'Dear Anthony',
+      'Postal Greeting' => 'Dear Anthony',
+      'Current Employer' => '',
+      'Location Type' => 'Home',
+      'Street Address' => 'Ambachtstraat 23',
+      'Street Number' => '',
+      'Street Number Suffix' => '',
+      'Street Name' => '',
+      'Street Unit' => '',
+      'Supplemental Address 1' => '',
+      'Supplemental Address 2' => '',
+      'Supplemental Address 3' => '',
+      'City' => 'Brummen',
+      'Postal Code Suffix' => '',
+      'Postal Code' => '6971 BN',
+      'Latitude' => '',
+      'Longitude' => '',
+      'Address Name' => '',
+      'Master Address Belongs To' => '',
+      'County' => '',
+      'State' => '',
+      'Country' => 'Netherlands',
+      'Phone' => '',
+      'Phone Extension' => '',
+      'Phone Type' => '',
+      'Email' => 'home@example.com',
+      'On Hold' => '',
+      'Use for Bulk Mail' => '',
+      'Signature Text' => '',
+      'Signature Html' => '',
+      'IM Provider' => '',
+      'IM Screen Name' => '',
+      'OpenID' => '',
+      'World Region' => 'Europe and Central Asia',
+      'Website' => '',
+      'Membership Type' => 'General',
+      'Test' => '',
+      'Is Pay Later' => '',
+      'Member Since' => '2007-01-21',
+      'Membership Start Date' => '2007-01-21',
+      'Membership Expiration Date' => '2007-12-21',
+      'Source' => 'Payment',
+      'Membership Status' => 'Expired',
+      'Membership ID' => '2',
+      'Primary Member ID' => '',
+      'max_related' => '',
+      'membership_recur_id' => '',
+      'Campaign ID' => '',
+      'member_is_override' => '',
+      'member_auto_renew' => '',
+      'Total Amount' => '100.00',
+      'Contribution Status' => 'Completed',
+      'Date Received' => '2019-07-25 07:34:23',
+      'Payment Method' => 'Credit Card',
+      'Transaction ID' => '',
+    ];
+    $this->assertExpectedOutput($expected, $row);
   }
 
   /**
@@ -170,15 +299,15 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
    */
   public function testExportComponentsActivity() {
     $this->setUpActivityExportData();
-    $selectedFields = array(
-      array('Individual', 'display_name'),
-      array('Individual', '5_a_b', 'display_name'),
-    );
+    $selectedFields = [
+      ['contact_type' => 'Individual', 'name' => 'display_name'],
+      ['contact_type' => 'Individual', 'relationship_type_id' => '5', 'relationship_direction' => 'a_b', 'name' => 'display_name'],
+    ];
 
     list($tableName) = CRM_Export_BAO_Export::exportComponents(
       FALSE,
       $this->activityIDs,
-      array(),
+      [],
       '`activity_date_time` desc',
       $selectedFields,
       NULL,
@@ -187,10 +316,10 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
       NULL,
       FALSE,
       FALSE,
-      array(
+      [
         'exportOption' => CRM_Export_Form_Select::ACTIVITY_EXPORT,
         'suppress_csv_for_testing' => TRUE,
-      )
+      ]
     );
 
     // delete the export temp table and component table
@@ -210,13 +339,13 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
    */
   public function testGetExportStructureArrays() {
     // This is how return properties are formatted internally within the function for passing to the BAO query.
-    $returnProperties = array(
+    $returnProperties = [
       'first_name' => 1,
       'last_name' => 1,
       'receive_date' => 1,
       'contribution_source' => 1,
-      'location' => array(
-        'Home' => array(
+      'location' => [
+        'Home' => [
           'street_address' => 1,
           'city' => 1,
           'country' => 1,
@@ -224,38 +353,29 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
           'im-1' => 1,
           'im_provider' => 1,
           'phone-1' => 1,
-        ),
-      ),
+        ],
+      ],
       'phone' => 1,
       'trxn_id' => 1,
       'contribution_id' => 1,
-    );
-
-    $contactRelationshipTypes = CRM_Contact_BAO_Relationship::getContactRelationshipType(
-      NULL,
-      NULL,
-      NULL,
-      NULL,
-      TRUE,
-      'name',
-      FALSE
-    );
+    ];
 
-    $query = new CRM_Contact_BAO_Query(array(), $returnProperties, NULL,
+    $query = new CRM_Contact_BAO_Query([], $returnProperties, NULL,
       FALSE, FALSE, CRM_Contact_BAO_Query::MODE_CONTRIBUTE,
       FALSE, TRUE, TRUE, NULL, 'AND'
     );
 
     list($select) = $query->query();
     $pattern = '/as `?([^`,]*)/';
-    $queryFieldAliases = array();
+    $queryFieldAliases = [];
     preg_match_all($pattern, $select, $queryFieldAliases, PREG_PATTERN_ORDER);
     $processor = new CRM_Export_BAO_ExportProcessor(CRM_Contact_BAO_Query::MODE_CONTRIBUTE, NULL, 'AND');
     $processor->setQueryFields($query->_fields);
+    $processor->setReturnProperties($returnProperties);
 
-    list($outputFields) = CRM_Export_BAO_Export::getExportStructureArrays($returnProperties, $processor, $contactRelationshipTypes, '');
+    list($outputFields) = $processor->getExportStructureArrays();
     foreach (array_keys($outputFields) as $fieldAlias) {
-      if ($fieldAlias == 'Home-country') {
+      if ($fieldAlias === 'Home-country') {
         $this->assertTrue(in_array($fieldAlias . '_id', $queryFieldAliases[1]), 'Country is subject to some funky translate so we make sure country id is present');
       }
       else {
@@ -267,15 +387,19 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
 
   /**
    * Set up some data for us to do testing on.
+   *
+   * @throws \CRM_Core_Exception
    */
   public function setUpContributionExportData() {
     $this->setUpContactExportData();
-    $this->contributionIDs[] = $this->contributionCreate(array('contact_id' => $this->contactIDs[0], 'trxn_id' => 'null', 'invoice_id' => 'null'));
-    $this->contributionIDs[] = $this->contributionCreate(array('contact_id' => $this->contactIDs[1], 'trxn_id' => 'null', 'invoice_id' => 'null'));
+    $this->contributionIDs[] = $this->contributionCreate(['contact_id' => $this->contactIDs[0], 'trxn_id' => 'null', 'invoice_id' => 'null', 'receive_date' => '2019-07-25 07:34:23']);
+    $this->contributionIDs[] = $this->contributionCreate(['contact_id' => $this->contactIDs[1], 'trxn_id' => 'null', 'invoice_id' => 'null', 'receive_date' => '2018-12-01 00:00:00']);
   }
 
   /**
    * Set up some data for us to do testing on.
+   *
+   * @throws \CRM_Core_Exception
    */
   public function setUpMembershipExportData() {
     $this->setUpContactExportData();
@@ -283,10 +407,10 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
     $this->contactMembershipCreate(['contact_id' => $this->contactIDs[0]]);
     $this->membershipIDs[] = $this->contactMembershipCreate(['contact_id' => $this->contactIDs[0]]);
     $this->setUpContributionExportData();
-    $this->callAPISuccess('membership_payment', 'create', array(
+    $this->callAPISuccess('membership_payment', 'create', [
       'contribution_id' => $this->contributionIDs[0],
       'membership_id' => $this->membershipIDs[0],
-    ));
+    ]);
     $this->callAPISuccess('LineItem', 'get', [
       'entity_table' => 'civicrm_membership',
       'membership_id' => $this->membershipIDs[0],
@@ -299,13 +423,13 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
    */
   public function setupCaseExportData() {
     $contactID1 = $this->individualCreate();
-    $contactID2 = $this->individualCreate(array(), 1);
+    $contactID2 = $this->individualCreate([], 1);
 
-    $case = $this->callAPISuccess('case', 'create', array(
+    $case = $this->callAPISuccess('case', 'create', [
       'case_type_id' => 1,
       'subject' => 'blah',
       'contact_id' => $contactID1,
-    ));
+    ]);
     $this->callAPISuccess('CaseContact', 'create', [
       'case_id' => $case['id'],
       'contact_id' => $contactID2,
@@ -317,16 +441,18 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
    */
   public function setUpActivityExportData() {
     $this->setUpContactExportData();
-    $this->activityIDs[] = $this->activityCreate(array('contact_id' => $this->contactIDs[0]))['id'];
+    $this->activityIDs[] = $this->activityCreate(['contact_id' => $this->contactIDs[0]])['id'];
   }
 
   /**
    * Set up some data for us to do testing on.
+   *
+   * @throws \CRM_Core_Exception
    */
   public function setUpContactExportData() {
     $this->contactIDs[] = $contactA = $this->individualCreate(['gender_id' => 'Female']);
     // Create address for contact A.
-    $params = array(
+    $params = [
       'contact_id' => $contactA,
       'location_type_id' => 'Home',
       'street_address' => 'Ambachtstraat 23',
@@ -334,28 +460,28 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
       'country_id' => '1152',
       'city' => 'Brummen',
       'is_primary' => 1,
-    );
+    ];
     $result = $this->callAPISuccess('address', 'create', $params);
     $addressId = $result['id'];
 
-    $this->callAPISuccess('email', 'create', array(
+    $this->callAPISuccess('email', 'create', [
       'id' => $this->callAPISuccessGetValue('Email', ['contact_id' => $params['contact_id'], 'return' => 'id']),
       'location_type_id' => 'Home',
       'email' => 'home@example.com',
       'is_primary' => 1,
-    ));
-    $this->callAPISuccess('email', 'create', array('contact_id' => $params['contact_id'], 'location_type_id' => 'Work', 'email' => 'work@example.com', 'is_primary' => 0));
+    ]);
+    $this->callAPISuccess('email', 'create', ['contact_id' => $params['contact_id'], 'location_type_id' => 'Work', 'email' => 'work@example.com', 'is_primary' => 0]);
 
     $params['is_primary'] = 0;
     $params['location_type_id'] = 'Work';
     $this->callAPISuccess('address', 'create', $params);
     $this->contactIDs[] = $contactB = $this->individualCreate();
 
-    $this->callAPISuccess('address', 'create', array(
+    $this->callAPISuccess('address', 'create', [
       'contact_id' => $contactB,
       'location_type_id' => "Home",
       'master_id' => $addressId,
-    ));
+    ]);
     $this->masterAddressID = $addressId;
 
   }
@@ -367,36 +493,32 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
    *
    * @dataProvider getBooleanDataProvider
    * @throws \CRM_Core_Exception
+   * @throws \League\Csv\Exception
    */
   public function testExportPrimaryAddress($isPrimaryOnly) {
     \Civi::settings()->set('searchPrimaryDetailsOnly', $isPrimaryOnly);
     $this->setUpContactExportData();
 
-    $selectedFields = [['Individual', 'email', ' '], ['Individual', 'email', '1'], ['Individual', 'email', '2']];
-    list($tableName) = CRM_Export_BAO_Export::exportComponents(
-      TRUE,
-      [],
-      [['email', 'LIKE', 'c', 0, 1]],
-      NULL,
-      $selectedFields,
-      NULL,
-      CRM_Export_Form_Select::CONTACT_EXPORT,
-      "contact_a.id IN ({$this->contactIDs[0]}, {$this->contactIDs[1]})",
-      NULL,
-      FALSE,
-      FALSE,
-      array(
-        'exportOption' => CRM_Export_Form_Select::CONTACT_EXPORT,
-        'suppress_csv_for_testing' => TRUE,
-      )
-    );
+    $selectedFields = [
+      ['contact_type' => 'Individual', 'name' => 'email'],
+      ['contact_type' => 'Individual', 'name' => 'email', 'location_type_id' => '1'],
+      ['contact_type' => 'Individual', 'name' => 'email', 'location_type_id' => '2'],
+    ];
+    $this->doExportTest([
+      'ids' => [],
+      'params' => [['email', 'LIKE', 'c', 0, 1]],
+      'fields' => $selectedFields,
+      'componentClause' => "contact_a.id IN ({$this->contactIDs[0]}, {$this->contactIDs[1]})",
+      'selectAll' => TRUE,
+    ]);
 
-    $dao = CRM_Core_DAO::executeQuery('SELECT * from ' . $tableName);
-    $dao->fetch();
-    $this->assertEquals('home@example.com', $dao->email);
-    $this->assertEquals('work@example.com', $dao->work_email);
-    $this->assertEquals('home@example.com', $dao->home_email);
-    $this->assertEquals(2, $dao->N);
+    $row = $this->csv->fetchOne();
+    $this->assertEquals([
+      'Email' => 'home@example.com',
+      'Home-Email' => 'home@example.com',
+      'Work-Email' => 'work@example.com',
+    ], $row);
+    $this->assertEquals(2, count($this->csv));
     \Civi::settings()->set('searchPrimaryDetailsOnly', FALSE);
   }
 
@@ -405,6 +527,8 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
    *
    * ie. we have a contact WITH a gender & one without - make sure the latter one
    * does NOT retain the gender of the former.
+   *
+   * @throws \CRM_Core_Exception
    */
   public function testExportPseudoField() {
     $this->setUpContactExportData();
@@ -422,11 +546,14 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
     $this->setUpContributionExportData();
     $campaign = $this->callAPISuccess('Campaign', 'create', ['title' => 'Big campaign']);
     $this->callAPISuccess('Contribution', 'create', ['campaign_id' => 'Big_campaign', 'id' => $this->contributionIDs[0]]);
-    $selectedFields = [['Individual', 'gender_id'], ['Contribution', 'contribution_campaign_title']];
+    $selectedFields = [
+      ['contact_type' => 'Individual', 'name' => 'gender_id'],
+      ['contact_type' => 'Contribution', 'name' => 'contribution_campaign_title'],
+    ];
     list($tableName, $sqlColumns) = CRM_Export_BAO_Export::exportComponents(
       TRUE,
       $this->contactIDs[1],
-      array(),
+      [],
       NULL,
       $selectedFields,
       NULL,
@@ -435,16 +562,18 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
       NULL,
       FALSE,
       FALSE,
-      array(
-        'exportOption' => CRM_Export_Form_Select::CONTACT_EXPORT,
+      [
         'suppress_csv_for_testing' => TRUE,
-      )
+      ]
     );
     $this->assertEquals('Big campaign,', CRM_Core_DAO::singleValueQuery("SELECT GROUP_CONCAT(contribution_campaign_title) FROM {$tableName}"));
   }
 
   /**
    * Test exporting relationships.
+   *
+   * @throws \CRM_Core_Exception
+   * @throws \League\Csv\Exception
    */
   public function testExportRelationships() {
     $organization1 = $this->organizationCreate(['organization_name' => 'Org 1', 'legal_name' => 'pretty legal', 'contact_source' => 'friend who took a law paper once']);
@@ -453,46 +582,26 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
     $contact2 = $this->individualCreate(['employer_id' => $organization2, 'first_name' => 'one']);
     $employerRelationshipTypeID = $this->callAPISuccessGetValue('RelationshipType', ['return' => 'id', 'label_a_b' => 'Employee of']);
     $selectedFields = [
-      ['Individual', 'first_name', ''],
-      ['Individual', $employerRelationshipTypeID . '_a_b', 'organization_name', ''],
-      ['Individual', $employerRelationshipTypeID . '_a_b', 'legal_name', ''],
-      ['Individual', $employerRelationshipTypeID . '_a_b', 'contact_source', ''],
+      ['contact_type' => 'Individual', 'name' => 'first_name'],
+      ['contact_type' => 'Individual', 'relationship_type_id' => $employerRelationshipTypeID, 'relationship_direction' => 'a_b', 'name' => 'organization_name'],
+      ['contact_type' => 'Individual', 'relationship_type_id' => $employerRelationshipTypeID, 'relationship_direction' => 'a_b', 'name' => 'legal_name'],
+      ['contact_type' => 'Individual', 'relationship_type_id' => $employerRelationshipTypeID, 'relationship_direction' => 'a_b', 'name' => 'contact_source'],
     ];
-    list($tableName, $sqlColumns, $headerRows) = CRM_Export_BAO_Export::exportComponents(
-      FALSE,
-      [$contact1, $contact2],
-      [],
-      NULL,
-      $selectedFields,
-      NULL,
-      CRM_Export_Form_Select::CONTACT_EXPORT,
-      "contact_a.id IN ( $contact1, $contact2 )",
-      NULL,
-      FALSE,
-      FALSE,
-      [
-        'exportOption' => CRM_Export_Form_Select::CONTACT_EXPORT,
-        'suppress_csv_for_testing' => TRUE,
-      ]
-    );
-
-    $dao = CRM_Core_DAO::executeQuery("SELECT * FROM {$tableName}");
-    $dao->fetch();
-    $this->assertEquals('one', $dao->first_name);
-    $this->assertEquals('Org 1', $dao->{$employerRelationshipTypeID . '_a_b_organization_name'});
-    $this->assertEquals('pretty legal', $dao->{$employerRelationshipTypeID . '_a_b_legal_name'});
-    $this->assertEquals('friend who took a law paper once', $dao->{$employerRelationshipTypeID . '_a_b_contact_source'});
+    $this->doExportTest([
+      'ids' => [$contact1, $contact2],
+      'componentClause' => "contact_a.id IN ( $contact1, $contact2 )",
+      'fields' => $selectedFields,
+    ]);
 
-    $dao->fetch();
-    $this->assertEquals('Org 2', $dao->{$employerRelationshipTypeID . '_a_b_organization_name'});
-    $this->assertEquals('well dodgey', $dao->{$employerRelationshipTypeID . '_a_b_legal_name'});
+    $row = $this->csv->fetchOne();
+    $this->assertEquals('one', $row['First Name']);
+    $this->assertEquals('Org 1', $row['Employee of-Organization Name']);
+    $this->assertEquals('pretty legal', $row['Employee of-Legal Name']);
+    $this->assertEquals('friend who took a law paper once', $row['Employee of-Contact Source']);
 
-    $this->assertEquals([
-      0 => 'First Name',
-      1 => 'Employee of-Organization Name',
-      2 => 'Employee of-Legal Name',
-      3 => 'Employee of-Contact Source',
-    ], $headerRows);
+    $row = $this->csv->fetchOne(1);
+    $this->assertEquals('Org 2', $row['Employee of-Organization Name']);
+    $this->assertEquals('well dodgey', $row['Employee of-Legal Name']);
   }
 
   /**
@@ -513,11 +622,11 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
       $this->contactIDs[] = $householdID;
     }
     $selectedFields = [
-      ['Individual', $houseHoldTypeID . '_a_b', 'state_province', ''],
-      ['Individual', $houseHoldTypeID . '_a_b', 'city', ''],
-      ['Individual', 'city', ''],
-      ['Individual', 'state_province', ''],
-      ['Individual', 'contact_source', ''],
+      ['contact_type' => 'Individual', 'relationship_type_id' => $houseHoldTypeID, 'relationship_direction' => 'a_b', 'name' => 'state_province', 'location_type_id' => ''],
+      ['contact_type' => 'Individual', 'relationship_type_id' => $houseHoldTypeID, 'relationship_direction' => 'a_b', 'name' => 'city', 'location_type_id' => ''],
+      ['contact_type' => 'Individual', 'name' => 'city', 'location_type_id' => ''],
+      ['contact_type' => 'Individual', 'name' => 'state_province', 'location_type_id' => ''],
+      ['contact_type' => 'Individual', 'name' => 'contact_source', 'location_type_id' => ''],
     ];
     list($tableName, $sqlColumns, $headerRows) = CRM_Export_BAO_Export::exportComponents(
       FALSE,
@@ -532,7 +641,6 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
       FALSE,
       TRUE,
       [
-        'exportOption' => CRM_Export_Form_Select::CONTACT_EXPORT,
         'suppress_csv_for_testing' => TRUE,
       ]
     );
@@ -579,7 +687,6 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
       FALSE,
       TRUE,
       [
-        'exportOption' => CRM_Export_Form_Select::CONTACT_EXPORT,
         'suppress_csv_for_testing' => TRUE,
       ]
     );
@@ -628,6 +735,9 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
 
   /**
    * Attempt to do a fairly full export of location data.
+   *
+   * @throws \CRM_Core_Exception
+   * @throws \League\Csv\Exception
    */
   public function testExportIMData() {
     // Use default providers.
@@ -705,125 +815,35 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
         }
       }
     }
-    list($tableName, $sqlColumns) = $this->doExport($fields, $this->contactIDs[0]);
 
-    $dao = CRM_Core_DAO::executeQuery('SELECT * FROM ' . $tableName);
-    while ($dao->fetch()) {
-      $id = $dao->contact_id;
-      $this->assertEquals('AIM', $dao->billing_im_provider);
-      $this->assertEquals('BillingJabber' . $id, $dao->billing_im_screen_name_jabber);
-      $this->assertEquals('BillingSkype' . $id, $dao->billing_im_screen_name_skype);
+    // @todo switch to just declaring the new format....
+    $mappedFields = [];
+    foreach ($fields as $field) {
+      $mappedFields[] = CRM_Core_BAO_Mapping::getMappingParams([], $field);
+    }
+    $this->doExportTest(['fields' => $mappedFields, 'ids' => [$this->contactIDs[0]]]);
+
+    foreach ($this->csv->getRecords() as $row) {
+      $id = $row['contact_id'];
+      $this->assertEquals('AIM', $row['Billing-IM Provider']);
+      $this->assertEquals('AIM', $row['Whare Kai-IM Provider']);
+      $this->assertEquals('BillingJabber' . $id, $row['Billing-IM Screen Name-Jabber']);
+      $this->assertEquals('Whare KaiJabber' . $id, $row['Whare Kai-IM Screen Name-Jabber']);
+      $this->assertEquals('BillingSkype' . $id, $row['Billing-IM Screen Name-Skype']);
       foreach ($relationships as $relatedContactID => $relationship) {
-        $relationshipString = $field = $relationship['relationship_type_id'] . '_a_b';
-        $field = $relationshipString . '_billing_im_screen_name_yahoo';
-        $this->assertEquals('BillingYahoo' . $relatedContactID, $dao->$field);
-        // @todo efforts to output 'im_provider' for related contacts seem to be giving a blank field.
+        $this->assertEquals('BillingYahoo' . $relatedContactID, $row[$relationship['label'] . '-Billing-IM Screen Name-Yahoo']);
+        $this->assertEquals('Whare KaiJabber' . $relatedContactID, $row[$relationship['label'] . '-Whare Kai-IM Screen Name-Jabber']);
       }
     }
-
-    $this->assertEquals([
-      'billing_im_provider' => 'billing_im_provider text',
-      'billing_im_screen_name' => 'billing_im_screen_name varchar(64)',
-      'billing_im_screen_name_jabber' => 'billing_im_screen_name_jabber varchar(64)',
-      'billing_im_screen_name_skype' => 'billing_im_screen_name_skype varchar(64)',
-      'billing_im_screen_name_yahoo' => 'billing_im_screen_name_yahoo varchar(64)',
-      'home_im_provider' => 'home_im_provider text',
-      'home_im_screen_name' => 'home_im_screen_name varchar(64)',
-      'home_im_screen_name_jabber' => 'home_im_screen_name_jabber varchar(64)',
-      'home_im_screen_name_skype' => 'home_im_screen_name_skype varchar(64)',
-      'home_im_screen_name_yahoo' => 'home_im_screen_name_yahoo varchar(64)',
-      'main_im_provider' => 'main_im_provider text',
-      'main_im_screen_name' => 'main_im_screen_name varchar(64)',
-      'main_im_screen_name_jabber' => 'main_im_screen_name_jabber varchar(64)',
-      'main_im_screen_name_skype' => 'main_im_screen_name_skype varchar(64)',
-      'main_im_screen_name_yahoo' => 'main_im_screen_name_yahoo varchar(64)',
-      'other_im_provider' => 'other_im_provider text',
-      'other_im_screen_name' => 'other_im_screen_name varchar(64)',
-      'other_im_screen_name_jabber' => 'other_im_screen_name_jabber varchar(64)',
-      'other_im_screen_name_skype' => 'other_im_screen_name_skype varchar(64)',
-      'other_im_screen_name_yahoo' => 'other_im_screen_name_yahoo varchar(64)',
-      'im_provider' => 'im_provider text',
-      'im_screen_name' => 'im_screen_name varchar(64)',
-      'contact_id' => 'contact_id varchar(255)',
-      '2_a_b_im_provider' => '2_a_b_im_provider text',
-      '2_a_b_billing_im_screen_name' => '2_a_b_billing_im_screen_name varchar(64)',
-      '2_a_b_billing_im_screen_name_jabber' => '2_a_b_billing_im_screen_name_jabber varchar(64)',
-      '2_a_b_billing_im_screen_name_skype' => '2_a_b_billing_im_screen_name_skype varchar(64)',
-      '2_a_b_billing_im_screen_name_yahoo' => '2_a_b_billing_im_screen_name_yahoo varchar(64)',
-      '2_a_b_home_im_screen_name' => '2_a_b_home_im_screen_name varchar(64)',
-      '2_a_b_home_im_screen_name_jabber' => '2_a_b_home_im_screen_name_jabber varchar(64)',
-      '2_a_b_home_im_screen_name_skype' => '2_a_b_home_im_screen_name_skype varchar(64)',
-      '2_a_b_home_im_screen_name_yahoo' => '2_a_b_home_im_screen_name_yahoo varchar(64)',
-      '2_a_b_main_im_screen_name' => '2_a_b_main_im_screen_name varchar(64)',
-      '2_a_b_main_im_screen_name_jabber' => '2_a_b_main_im_screen_name_jabber varchar(64)',
-      '2_a_b_main_im_screen_name_skype' => '2_a_b_main_im_screen_name_skype varchar(64)',
-      '2_a_b_main_im_screen_name_yahoo' => '2_a_b_main_im_screen_name_yahoo varchar(64)',
-      '2_a_b_other_im_screen_name' => '2_a_b_other_im_screen_name varchar(64)',
-      '2_a_b_other_im_screen_name_jabber' => '2_a_b_other_im_screen_name_jabber varchar(64)',
-      '2_a_b_other_im_screen_name_skype' => '2_a_b_other_im_screen_name_skype varchar(64)',
-      '2_a_b_other_im_screen_name_yahoo' => '2_a_b_other_im_screen_name_yahoo varchar(64)',
-      '2_a_b_im_screen_name' => '2_a_b_im_screen_name varchar(64)',
-      '8_a_b_im_provider' => '8_a_b_im_provider text',
-      '8_a_b_billing_im_screen_name' => '8_a_b_billing_im_screen_name varchar(64)',
-      '8_a_b_billing_im_screen_name_jabber' => '8_a_b_billing_im_screen_name_jabber varchar(64)',
-      '8_a_b_billing_im_screen_name_skype' => '8_a_b_billing_im_screen_name_skype varchar(64)',
-      '8_a_b_billing_im_screen_name_yahoo' => '8_a_b_billing_im_screen_name_yahoo varchar(64)',
-      '8_a_b_home_im_screen_name' => '8_a_b_home_im_screen_name varchar(64)',
-      '8_a_b_home_im_screen_name_jabber' => '8_a_b_home_im_screen_name_jabber varchar(64)',
-      '8_a_b_home_im_screen_name_skype' => '8_a_b_home_im_screen_name_skype varchar(64)',
-      '8_a_b_home_im_screen_name_yahoo' => '8_a_b_home_im_screen_name_yahoo varchar(64)',
-      '8_a_b_main_im_screen_name' => '8_a_b_main_im_screen_name varchar(64)',
-      '8_a_b_main_im_screen_name_jabber' => '8_a_b_main_im_screen_name_jabber varchar(64)',
-      '8_a_b_main_im_screen_name_skype' => '8_a_b_main_im_screen_name_skype varchar(64)',
-      '8_a_b_main_im_screen_name_yahoo' => '8_a_b_main_im_screen_name_yahoo varchar(64)',
-      '8_a_b_other_im_screen_name' => '8_a_b_other_im_screen_name varchar(64)',
-      '8_a_b_other_im_screen_name_jabber' => '8_a_b_other_im_screen_name_jabber varchar(64)',
-      '8_a_b_other_im_screen_name_skype' => '8_a_b_other_im_screen_name_skype varchar(64)',
-      '8_a_b_other_im_screen_name_yahoo' => '8_a_b_other_im_screen_name_yahoo varchar(64)',
-      '8_a_b_im_screen_name' => '8_a_b_im_screen_name varchar(64)',
-      '5_a_b_im_provider' => '5_a_b_im_provider text',
-      '5_a_b_billing_im_screen_name' => '5_a_b_billing_im_screen_name varchar(64)',
-      '5_a_b_billing_im_screen_name_jabber' => '5_a_b_billing_im_screen_name_jabber varchar(64)',
-      '5_a_b_billing_im_screen_name_skype' => '5_a_b_billing_im_screen_name_skype varchar(64)',
-      '5_a_b_billing_im_screen_name_yahoo' => '5_a_b_billing_im_screen_name_yahoo varchar(64)',
-      '5_a_b_home_im_screen_name' => '5_a_b_home_im_screen_name varchar(64)',
-      '5_a_b_home_im_screen_name_jabber' => '5_a_b_home_im_screen_name_jabber varchar(64)',
-      '5_a_b_home_im_screen_name_skype' => '5_a_b_home_im_screen_name_skype varchar(64)',
-      '5_a_b_home_im_screen_name_yahoo' => '5_a_b_home_im_screen_name_yahoo varchar(64)',
-      '5_a_b_main_im_screen_name' => '5_a_b_main_im_screen_name varchar(64)',
-      '5_a_b_main_im_screen_name_jabber' => '5_a_b_main_im_screen_name_jabber varchar(64)',
-      '5_a_b_main_im_screen_name_skype' => '5_a_b_main_im_screen_name_skype varchar(64)',
-      '5_a_b_main_im_screen_name_yahoo' => '5_a_b_main_im_screen_name_yahoo varchar(64)',
-      '5_a_b_other_im_screen_name' => '5_a_b_other_im_screen_name varchar(64)',
-      '5_a_b_other_im_screen_name_jabber' => '5_a_b_other_im_screen_name_jabber varchar(64)',
-      '5_a_b_other_im_screen_name_skype' => '5_a_b_other_im_screen_name_skype varchar(64)',
-      '5_a_b_other_im_screen_name_yahoo' => '5_a_b_other_im_screen_name_yahoo varchar(64)',
-      '5_a_b_im_screen_name' => '5_a_b_im_screen_name varchar(64)',
-      'whare_kai_im_provider' => 'whare_kai_im_provider text',
-      'whare_kai_im_screen_name' => 'whare_kai_im_screen_name varchar(64)',
-      'whare_kai_im_screen_name_jabber' => 'whare_kai_im_screen_name_jabber varchar(64)',
-      'whare_kai_im_screen_name_skype' => 'whare_kai_im_screen_name_skype varchar(64)',
-      'whare_kai_im_screen_name_yahoo' => 'whare_kai_im_screen_name_yahoo varchar(64)',
-      '2_a_b_whare_kai_im_screen_name' => '2_a_b_whare_kai_im_screen_name varchar(64)',
-      '2_a_b_whare_kai_im_screen_name_jabber' => '2_a_b_whare_kai_im_screen_name_jabber varchar(64)',
-      '2_a_b_whare_kai_im_screen_name_skype' => '2_a_b_whare_kai_im_screen_name_skype varchar(64)',
-      '2_a_b_whare_kai_im_screen_name_yahoo' => '2_a_b_whare_kai_im_screen_name_yahoo varchar(64)',
-      '8_a_b_whare_kai_im_screen_name' => '8_a_b_whare_kai_im_screen_name varchar(64)',
-      '8_a_b_whare_kai_im_screen_name_jabber' => '8_a_b_whare_kai_im_screen_name_jabber varchar(64)',
-      '8_a_b_whare_kai_im_screen_name_skype' => '8_a_b_whare_kai_im_screen_name_skype varchar(64)',
-      '8_a_b_whare_kai_im_screen_name_yahoo' => '8_a_b_whare_kai_im_screen_name_yahoo varchar(64)',
-      '5_a_b_whare_kai_im_screen_name' => '5_a_b_whare_kai_im_screen_name varchar(64)',
-      '5_a_b_whare_kai_im_screen_name_jabber' => '5_a_b_whare_kai_im_screen_name_jabber varchar(64)',
-      '5_a_b_whare_kai_im_screen_name_skype' => '5_a_b_whare_kai_im_screen_name_skype varchar(64)',
-      '5_a_b_whare_kai_im_screen_name_yahoo' => '5_a_b_whare_kai_im_screen_name_yahoo varchar(64)',
-    ], $sqlColumns);
-
   }
 
   /**
    * Test phone data export.
    *
    * Less over the top complete than the im test.
+   *
+   * @throws \CRM_Core_Exception
+   * @throws \League\Csv\Exception
    */
   public function testExportPhoneData() {
     $this->contactIDs[] = $this->individualCreate();
@@ -900,17 +920,18 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
         }
       }
     }
-    list($tableName) = $this->doExport($fields, $this->contactIDs[0]);
-
-    $dao = CRM_Core_DAO::executeQuery('SELECT * FROM ' . $tableName);
-    while ($dao->fetch()) {
-      // note there is some chance these might be random on some mysql co
-      $this->assertEquals('BillingMobile3', $dao->billing_phone_mobile);
-      $this->assertEquals('', $dao->billing_phone_phone);
-      $relField = '2_a_b_phone_type_id';
-      $this->assertEquals('Phone', $dao->$relField);
-      $this->assertEquals('Mobile', $dao->phone_type_id);
-      $this->assertEquals('Mobile', $dao->billing_phone_type_id);
+    // @todo switch to just declaring the new format....
+    $mappedFields = [];
+    foreach ($fields as $field) {
+      $mappedFields[] = CRM_Core_BAO_Mapping::getMappingParams([], $field);
+    }
+    $this->doExportTest(['fields' => $mappedFields, 'ids' => [$this->contactIDs[0]]]);
+    foreach ($this->csv->getRecords() as $row) {
+      $this->assertEquals('BillingMobile3', $row['Billing-Phone-Mobile']);
+      $this->assertEquals('', $row['Billing-Phone-Phone']);
+      $this->assertEquals('Phone', $row['Spouse of-Phone Type']);
+      $this->assertEquals('Mobile', $row['Phone Type']);
+      $this->assertEquals('Mobile', $row['Billing-Phone Type']);
     }
   }
 
@@ -1030,13 +1051,13 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
     $this->setUpContactExportData();
 
     //export the master address for contact B
-    $selectedFields = array(
-      array('Individual', 'master_id', 1),
-    );
+    $selectedFields = [
+      ['contact_type' => 'Individual', 'name' => 'master_id', 'location_type_id' => 1],
+    ];
     list($tableName, $sqlColumns) = CRM_Export_BAO_Export::exportComponents(
       TRUE,
-      array($this->contactIDs[1]),
-      array(),
+      [$this->contactIDs[1]],
+      [],
       NULL,
       $selectedFields,
       NULL,
@@ -1045,10 +1066,9 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
       NULL,
       FALSE,
       FALSE,
-      array(
-        'exportOption' => CRM_Export_Form_Select::CONTACT_EXPORT,
+      [
         'suppress_csv_for_testing' => TRUE,
-      )
+      ]
     );
     $field = key($sqlColumns);
 
@@ -1062,6 +1082,117 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
     CRM_Core_DAO::executeQuery($sql);
   }
 
+  /**
+   * Test the merge same address option.
+   *
+   * @throws \CRM_Core_Exception
+   * @throws \League\Csv\Exception
+   */
+  public function testMergeSameAddress() {
+    $this->setUpContactExportData();
+    $this->contactIDs[] = $contact3 = $this->individualCreate(['first_name' => 'Sarah', 'last_name' => 'Smith', 'prefix_id' => 'Dr.']);
+    // Create address for contact A.
+    $params = [
+      'contact_id' => $contact3,
+      'location_type_id' => 'Home',
+      'street_address' => 'Ambachtstraat 23',
+      'postal_code' => '6971 BN',
+      'country_id' => '1152',
+      'city' => 'Brummen',
+      'is_primary' => 1,
+    ];
+    $this->callAPISuccess('address', 'create', $params);
+    $this->doExportTest(['mergeSameAddress' => TRUE, 'ids' => $this->contactIDs]);
+    // ie 2 merged, one extra.
+    $this->assertCount(2, $this->csv);
+    $expected = [
+      'Contact ID' => $this->contactIDs[0],
+      'Contact Type' => 'Individual',
+      'Contact Subtype' => '',
+      'Do Not Email' => '',
+      'Do Not Phone' => '',
+      'Do Not Mail' => '',
+      'Do Not Sms' => '',
+      'Do Not Trade' => '',
+      'No Bulk Emails (User Opt Out)' => '',
+      'Legal Identifier' => '',
+      'External Identifier' => '',
+      'Sort Name' => 'Anderson, Anthony',
+      'Display Name' => 'Mr. Anthony Anderson II',
+      'Nickname' => '',
+      'Legal Name' => '',
+      'Image Url' => '',
+      'Preferred Communication Method' => '',
+      'Preferred Language' => 'en_US',
+      'Preferred Mail Format' => 'Both',
+      'Contact Hash' => 'e9bd0913cc05cc5aeae69ba04ee3be84',
+      'Contact Source' => '',
+      'First Name' => 'Anthony',
+      'Middle Name' => 'J.',
+      'Last Name' => 'Anderson',
+      'Individual Prefix' => 'Mr.',
+      'Individual Suffix' => 'II',
+      'Formal Title' => '',
+      'Communication Style' => 'Formal',
+      'Email Greeting ID' => '1',
+      'Postal Greeting ID' => '1',
+      'Addressee ID' => '1',
+      'Job Title' => '',
+      'Gender' => 'Female',
+      'Birth Date' => '',
+      'Deceased' => '',
+      'Deceased Date' => '',
+      'Household Name' => '',
+      'Organization Name' => '',
+      'Sic Code' => '',
+      'Unique ID (OpenID)' => '',
+      'Current Employer ID' => '',
+      'Contact is in Trash' => '',
+      'Created Date' => '2019-07-11 10:28:15',
+      'Modified Date' => '2019-07-11 10:28:15',
+      'Addressee' => 'Mr. Anthony J. Anderson II, Dr. Sarah J. Smith II',
+      'Email Greeting' => 'Dear Anthony, Sarah',
+      'Postal Greeting' => 'Dear Anthony, Sarah',
+      'Current Employer' => '',
+      'Location Type' => 'Home',
+      'Street Address' => 'Ambachtstraat 23',
+      'Street Number' => '',
+      'Street Number Suffix' => '',
+      'Street Name' => '',
+      'Street Unit' => '',
+      'Supplemental Address 1' => '',
+      'Supplemental Address 2' => '',
+      'Supplemental Address 3' => '',
+      'City' => 'Brummen',
+      'Postal Code Suffix' => '',
+      'Postal Code' => '6971 BN',
+      'Latitude' => '',
+      'Longitude' => '',
+      'Address Name' => '',
+      'Master Address Belongs To' => '',
+      'County' => '',
+      'State' => '',
+      'Country' => 'Netherlands',
+      'Phone' => '',
+      'Phone Extension' => '',
+      'Phone Type' => '',
+      'Email' => 'home@example.com',
+      'On Hold' => '',
+      'Use for Bulk Mail' => '',
+      'Signature Text' => '',
+      'Signature Html' => '',
+      'IM Provider' => '',
+      'IM Screen Name' => '',
+      'OpenID' => '',
+      'World Region' => 'Europe and Central Asia',
+      'Website' => '',
+      'Group(s)' => '',
+      'Tag(s)' => '',
+      'Note(s)' => '',
+    ];
+    $this->assertExpectedOutput($expected, $this->csv->fetchOne());
+  }
+
   /**
    * Test that deceased and do not mail contacts are removed from contacts before
    *
@@ -1069,13 +1200,15 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
    *
    * @param array $reason
    * @param array $addressReason
+   *
+   * @throws \CRM_Core_Exception
    */
   public function testExportDeceasedDoNotMail($reason, $addressReason) {
-    $contactA = $this->callAPISuccess('contact', 'create', array(
+    $contactA = $this->callAPISuccess('contact', 'create', [
       'first_name' => 'John',
       'last_name' => 'Doe',
       'contact_type' => 'Individual',
-    ));
+    ]);
 
     $contactB = $this->callAPISuccess('contact', 'create', array_merge([
       'first_name' => 'Jane',
@@ -1108,8 +1241,8 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
     //export and merge contacts with same address
     list($tableName, $sqlColumns, $headerRows, $processor) = CRM_Export_BAO_Export::exportComponents(
       TRUE,
-      array($contactA['id'], $contactB['id']),
-      array(),
+      [$contactA['id'], $contactB['id']],
+      [],
       NULL,
       NULL,
       NULL,
@@ -1118,14 +1251,13 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
       NULL,
       TRUE,
       FALSE,
-      array(
-        'exportOption' => CRM_Export_Form_Select::CONTACT_EXPORT,
+      [
         'mergeOption' => TRUE,
         'suppress_csv_for_testing' => TRUE,
-        'postal_mailing_export' => array(
+        'postal_mailing_export' => [
           'postal_mailing_export' => TRUE,
-        ),
-      )
+        ],
+      ]
     );
 
     $this->assertTrue(!in_array('state_province_id', $processor->getHeaderRows()));
@@ -1141,6 +1273,7 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
 
   /**
    * Get reasons that a contact is not postalable.
+   *
    * @return array
    */
   public function getReasonsNotToMail() {
@@ -1152,7 +1285,10 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
   }
 
   /**
+   * Set up household for tests.
+   *
    * @return array
+   * @throws \Exception
    */
   protected function setUpHousehold() {
     $this->setUpContactExportData();
@@ -1182,39 +1318,43 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
       'contact_id_b' => $householdID,
       'relationship_type_id' => $houseHoldTypeID,
     ]);
-    return array($householdID, $houseHoldTypeID);
+    return [$householdID, $houseHoldTypeID];
   }
 
   /**
    * Do a CiviCRM export.
    *
-   * @param $selectedFields
+   * @param array $selectedFields
    * @param int $id
    *
    * @param int $exportMode
    *
    * @return array
+   * @throws \CRM_Core_Exception
    */
   protected function doExport($selectedFields, $id, $exportMode = CRM_Export_Form_Select::CONTACT_EXPORT) {
     $ids = (array) $id;
+    $mappedFields = [];
+    foreach ((array) $selectedFields as $field) {
+      $mappedFields[] = CRM_Core_BAO_Mapping::getMappingParams([], $field);
+    }
     list($tableName, $sqlColumns) = CRM_Export_BAO_Export::exportComponents(
       TRUE,
       $ids,
-      array(),
+      [],
       NULL,
-      $selectedFields,
+      $mappedFields,
       NULL,
       $exportMode,
       "contact_a.id IN (" . implode(',', $ids) . ")",
       NULL,
       FALSE,
       FALSE,
-      array(
-        'exportOption' => CRM_Export_Form_Select::CONTACT_EXPORT,
+      [
         'suppress_csv_for_testing' => TRUE,
-      )
+      ]
     );
-    return array($tableName, $sqlColumns);
+    return [$tableName, $sqlColumns];
   }
 
   /**
@@ -1232,6 +1372,7 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
    * Test our export all field metadata retrieval.
    *
    * @dataProvider additionalFieldsDataProvider
+   *
    * @param int $exportMode
    * @param $expected
    */
@@ -1246,6 +1387,7 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
    * Test our export all field metadata retrieval.
    *
    * @dataProvider allFieldsDataProvider
+   *
    * @param int $exportMode
    * @param $expected
    */
@@ -1661,6 +1803,7 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
    * @param array $expected
    * @param array $expectedHeaders
    *
+   * @throws \CRM_Core_Exception
    * @dataProvider getSqlColumnsOutput
    */
   public function testGetSQLColumnsAndHeaders($exportMode, $expected, $expectedHeaders) {
@@ -1683,10 +1826,9 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
       NULL,
       FALSE,
       FALSE,
-      array(
-        'exportOption' => CRM_Export_Form_Select::CONTRIBUTE_EXPORT,
+      [
         'suppress_csv_for_testing' => TRUE,
-      )
+      ]
     );
     $this->assertEquals($expected, $result[1]);
     $this->assertEquals($expectedHeaders, $result[2]);
@@ -1743,9 +1885,10 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
   }
 
   /**
-   * Get all return fields (@todo - still being built up.
+   * Get all return fields (@return array
+   *
+   * @todo - still being built up.
    *
-   * @return array
    */
   public function getAllSpecifiableReturnFields() {
     return [
@@ -2709,4 +2852,64 @@ class CRM_Export_BAO_ExportTest extends CiviUnitTestCase {
     ]);
   }
 
+  /**
+   * Test export components.
+   *
+   * Tests the exportComponents function with the provided parameters.
+   *
+   * This exportComponents will export a csv but it will also throw a prematureExitException
+   * which we catch & grab the processor from.
+   *
+   * $this->processor is set to the export processor.
+   *
+   * @param $params
+   *
+   * @throws \CRM_Core_Exception
+   * @throws \League\Csv\Exception
+   */
+  protected function doExportTest($params) {
+    $this->startCapturingOutput();
+    try {
+      $defaultClause = (empty($params['ids']) ? NULL : "contact_a.id IN (" . implode(',', $params['ids']) . ")");
+
+      CRM_Export_BAO_Export::exportComponents(
+        CRM_Utils_Array::value('selectAll', $params, (empty($params['fields']))),
+        CRM_Utils_Array::value('ids', $params, []),
+        CRM_Utils_Array::value('params', $params, []),
+        CRM_Utils_Array::value('order', $params),
+        CRM_Utils_Array::value('fields', $params, []),
+        CRM_Utils_Array::value('moreReturnProperties', $params),
+        CRM_Utils_Array::value('exportMode', $params, CRM_Export_Form_Select::CONTACT_EXPORT),
+        CRM_Utils_Array::value('componentClause', $params, $defaultClause),
+        CRM_Utils_Array::value('componentTable', $params),
+        CRM_Utils_Array::value('mergeSameAddress', $params, FALSE),
+        CRM_Utils_Array::value('mergeSameHousehold', $params, FALSE)
+      );
+    }
+    catch (CRM_Core_Exception_PrematureExitException $e) {
+      $this->processor = $e->errorData['processor'];
+      $this->csv = $this->captureOutputToCSV();
+      return;
+    }
+    $this->fail('We expected a premature exit exception');
+  }
+
+  /**
+   * Assert that the received array matches the expected, ignoring time sensitive fields.
+   *
+   * @param array $expected
+   * @param array $row
+   */
+  protected function assertExpectedOutput(array $expected, array $row) {
+    $variableFields = ['Created Date', 'Modified Date', 'Contact Hash'];
+    foreach ($expected as $key => $value) {
+      if (in_array($key, $variableFields)) {
+        $this->assertTrue(!empty($row[$key]));
+      }
+      else {
+        $this->assertEquals($value, $row[$key]);
+      }
+    }
+  }
+
 }