allBlogsList

CloudCraze Portal User Simulation and Storefront Setup for Tests

It's not uncommon for particular CloudCraze API calls to fail within a test because they aren't executed as a community portal user. One example of this, that we recently encountered, is with regard to the `ccrz.ccApiWishList.create()` method. In order to address scenarios like these we'd need to simulate a CloudCraze Community Portal User. There are a number of challenges that make creating a portal user difficult.

For one, `User.isPortalEnabled` cannot be assigned, since it isn’t writable. So even if a user record were created, that record wouldn’t be of any use without portal access. In addition, the task of creating the portal user also cannot be delegated to `Site.createExternalUser()` since this method returns a null user ID inside the context of a test. The `ccrz.ccAPIUser.registerUser()` method wraps the `Site.createExternalUser()` method, so it too, is not helpful  

Although this general problem can be dismissed by setting the `@IsTest(SeeAllData=true)` annotation to open access to existing records of portal users, in a typical unit testing scenario, it is assumed that the database remains isolated. In accordance with best practice, it’s always best to avoid using the `SeeAllData=true` annotation because it opens up the database for unexpected modification by tests. Also, writing tests that rely on the organization database does not ensure that the code will continue to work as expected when it is deployed to production, and run with a different database. This example code addresses how we implement this while keeping the database isolated from the tests `(SeeAllData=false)`:

Profile communityUserProfile = [SELECT Id FROM Profile WHERE Name='CloudCraze Customer Community User' LIMIT 1];

Map<String, Map<String, Object>> testData = new Map<String, Map<String, Object>>{
  ccrz.ccApiTestData.ACCOUNTGROUP_DATA => new Map<String, Object>{},
  ccrz.ccApiTestData.ACCOUNT_DATA => new Map<String, Object>{
    ccrz.ccApiTestData.ACCOUNT_LIST => new List<Map<String, Object>>{
      new Map<String, Object>{
        'name' => 'testAccount1',
        'ccrz__dataId__c' => 'testAccount1'
      }
    }
  },
  ccrz.ccApiTestData.CONTACT_DATA => new Map<String, Object>{
    ccrz.ccApiTestData.CONTACT_LIST => new List<Map<String, Object>>{
      new Map<String, Object>{
        'ccrz__dataId__c' => 'testContact1',
        'account' => new Account(ccrz__dataId__c = 'testAccount1'),
        'email' => 'testcontact1.ccrz@cloudcraze.com',
        'lastName' => 'User1',
        'firstName' => 'Test1'
      }
    }
  },
  ccrz.ccApiTestData.USER_DATA => new Map<String, Object>{
    ccrz.ccApiTestData.USER_LIST => new List<Map<String, Object>>{
      new Map<String, Object>{
        'ccrz__dataId__c' => 'testUser1',
        'alias' => 'defusr1',
        'email' => 'test.ccrz1@cloudcraze.com',
        'lastName' => 'User1',
        'firstName' => 'Test1',
        'languageLocaleKey' => 'fr',
        'localeSIDKey' => 'fr_FR',
        'emailEncodingKey' => 'UTF-8',
        'profileId' => communityUserProfile.Id,
        'username' => System.currentTimeMillis() + 'test1@cloudcraze.com',
        'ccrz__CC_CurrencyCode__c' => 'EUR',
        'contact' => new Contact(ccrz__dataId__c = 'testContact1'),
        'timezoneSIDKey' => 'GMT'
      }
    }
  }
};
ccrz.ccApiTestData.setupData(testData); 

In brief: The `ccrz.ApiTestData()` API creates the Contact-Account-User records in a single go, with all the relationships handled. Tests also have access to profile records even without the `SeeAllData=true` attribute. So the profile can be queried and assigned to this newly created user. Then `Test.runAs(user)`, and build and initialize the context using the user and account records before the CCRZ method is invoked. When the method executes, the dependencies will be supplied by the context.

Ideally, this code should be invoked within the context of a system administrator with a role, using `Test.RunAs(user)`. This will avoid throwing the "portal account owner must have a role" exception if the tests are run by a user without a role. In our projects we abstract portal user creation into a utility handler that can be invoked inside the body an @IsTest or @TestSetup test method.

In addition, since some of the CCRZ global API methods require that you have a storefront bound to the context, if your tests are using `(SeeAllData=false)` - either implicitly or explicitly - you won’t have access to a storefront, and you’ll need to create one. Storefronts cannot be created using an object constructor, like with typical SObjects, since storefronts are not stored as SObjects. The ccrz.ccApiTestData API has support for storefront creation and configuration (even for configuring specific logic and service overrides for tests), and it’s advisable that you use it. Here’s a utility method that we use, specifically for this purpose:

public static Map<String, Object> createStorefront(String storefrontName) {
  Map<String, Map<String, Object>> storefrontData = new Map <String, Map<String, Object>> {
    ccrz.ccApiTestData.STOREFRONT_SETTINGS => new Map<String, Object> {
        storefrontName => new Map<String, Object> {
            'Allow_Anonymous_Browsing__c' => true,
            'Currencies__c' => 'USD',
            'Languages__c' => 'en_US',
            'Customer_Portal_Account_Name__c' => 'PortalAccount',
            'CustomerPortalAcctGroupName__c' => 'PortalAccount',
            'DefaultCurrencyCode__c' => 'USD',
            'DefaultLocale__c' => 'en_US',
            'InventoryCheckFlag__c' => false,
            'Quoting_Enabled__c' => true,
            'Skip_Tax_Calculation__c' => false,
            'Filter_Orders_Based_on_Owner__c' => true
        }
    }
  };
  return ccrz.ccApiTestData.setupData(storefrontData);
}

View more blogs and tutorials about
Salesforce B2B Commerce Cloud

Learn more about our Salesforce B2B Commerce Cloud work

For thought leadership on B2B Commerce,
please click here for blogs and here for video.