Skip to content

Commit

Permalink
Made some progress towards #193 - Added LoggerSObjectTestDataGenerato…
Browse files Browse the repository at this point in the history
…r class to simplify generating test SObject records, cleaned up LoggerTestUtils class
  • Loading branch information
jongpie committed Mar 27, 2022
1 parent 9b84e87 commit bd1c316
Show file tree
Hide file tree
Showing 14 changed files with 431 additions and 636 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
//------------------------------------------------------------------------------------------------//
// This file is part of the Nebula Logger project, released under the MIT License. //
// See LICENSE file or go to https://github.com/jongpie/NebulaLogger for full license details. //
//------------------------------------------------------------------------------------------------//

/**
* @group Configuration
* @description Factory class used to create or update an `SObject` record with static fake data.
* This is useful in situations where you need to have fields populated, but the specific
* values used are not relevant to a particular test.
* This class can be used when Apex writing tests for plugins.
* @see LoggerTestUtils
*/
@IsTest
public without sharing class LoggerSObjectTestDataGenerator {
private static final Map<Schema.SObjectType, List<Schema.SObjectField>> SOBJECT_TYPE_TO_ALL_FIELDS = new Map<Schema.SObjectType, List<Schema.SObjectField>>();
private static final Map<Schema.SObjectType, List<Schema.SObjectField>> SOBJECT_TYPE_TO_REQUIRED_FIELDS = new Map<Schema.SObjectType, List<Schema.SObjectField>>();

private final Schema.SObjectType sobjectType;
private final SObject record;

/**
* @description Creates a new generator instance for the specified `SObjectType`
* @param sobjectType The `SObjectType` to use for generating a new test `SObject` record
*/
public LoggerSObjectTestDataGenerator(Schema.SObjectType sobjectType) {
this(sobjectType.newSObject(null, true));
}

/**
* @description Creates a new generator instance for the specified `SObject` record
* @param record The existing test `SObject` record to populate with sample data
*/
public LoggerSObjectTestDataGenerator(SObject record) {
this.record = record;
this.sobjectType = record.getSObjectType();

this.loadFields();
}

/**
* @description Sets a value on all editable fields, unless the `SObject` record already had a value specified for a field (including `null`)
* @return The `SObject` record, with all editable fields populated
*/
public SObject populateAllFields() {
this.setNullFieldsOnRecord(SOBJECT_TYPE_TO_ALL_FIELDS.get(this.sobjectType));
return this.record;
}

/**
* @description Sets a value on all editable required fields, unless the `SObject` record already had a value specified for a field (including `null`)
* @return The `SObject` record, with all editable required fields populated
*/
public SObject populateRequiredFields() {
this.setNullFieldsOnRecord(SOBJECT_TYPE_TO_REQUIRED_FIELDS.get(this.sobjectType));
return this.record;
}

private void loadFields() {
if (SOBJECT_TYPE_TO_ALL_FIELDS.containsKey(this.sobjectType) == true && SOBJECT_TYPE_TO_REQUIRED_FIELDS.containsKey(this.sobjectType) == true) {
return;
}

SOBJECT_TYPE_TO_ALL_FIELDS.put(this.sobjectType, new List<Schema.SObjectField>());
SOBJECT_TYPE_TO_REQUIRED_FIELDS.put(this.sobjectType, new List<Schema.SObjectField>());
for (Schema.SObjectField field : this.sobjectType.getDescribe().fields.getMap().values()) {
if (field.getDescribe().isCreateable() == false) {
continue;
}

SOBJECT_TYPE_TO_ALL_FIELDS.get(this.sobjectType).add(field);
if (field.getDescribe().isNillable() == false) {
// If a field is not nillable & it is createable, then it's required
SOBJECT_TYPE_TO_REQUIRED_FIELDS.get(this.sobjectType).add(field);
}
}
}

// Helper methods
private void setNullFieldsOnRecord(List<Schema.SObjectField> fields) {
Map<String, Object> populatedFields = this.record.getPopulatedFieldsAsMap();
for (Schema.SObjectField field : fields) {
Schema.DescribeFieldResult fieldDescribe = field.getDescribe();
// If a field was already populated by using the constructor 'TestDataFactory(SObject record)', then don't change it
if (populatedFields.containsKey(fieldDescribe.getName())) {
continue;
}

Object fieldValue;
if (fieldDescribe.getDefaultValue() != null) {
// If there is a default value setup for the field, use it
fieldValue = fieldDescribe.getDefaultValue();
} else {
// Otherwise, we'll generate our own test value to use, based on the field's metadata
fieldValue = this.getTestValue(fieldDescribe);
}

// If we now have a value to use, set it on the record
if (fieldValue != null) {
this.record.put(field, fieldValue);
}
}
}

private Object getTestValue(Schema.DescribeFieldResult fieldDescribe) {
// Since Apex does not support case statements, we use several ugly IF-ELSE statements
// Some more complex data types, like ID & Reference, require other objects to be created
// This implementation delegates that responsibility to the test classes since DML is required to get a valid ID,
// but the logic below could be updated to support creating parent objects if needed

// Unsupported display types have been commented-out below
/*
Schema.DisplayType.Address, Schema.DisplayType.AnyType, Schema.DisplayType.Base64,
Schema.DisplayType.DataCategoryGroupReference, Schema.DisplayType.Id, Schema.DisplayType.Reference
*/
switch on fieldDescribe.getType() {
when Boolean {
return false;
}
when Combobox {
return this.getStringValue(fieldDescribe);
}
when Currency {
return 19.85;
}
when Date {
return System.today();
}
when Datetime {
return System.now();
}
when Double {
return (3.14).setScale(fieldDescribe.getScale());
}
when Email {
return '[email protected]';
}
when EncryptedString {
return this.getStringValue(fieldDescribe);
}
when Integer {
return 1;
}
when MultiPicklist {
return fieldDescribe.getPicklistValues()[0].getValue();
}
when Percent {
return 0.42;
}
when Phone {
return '+34 999 11 22 33';
}
when Picklist {
return fieldDescribe.getPicklistValues()[0].getValue();
}
when String {
return this.getStringValue(fieldDescribe);
}
when TextArea {
return this.getStringValue(fieldDescribe);
}
when Time {
return Time.newInstance(13, 30, 6, 20);
}
when Url {
return 'https://salesforce.com';
}
when else {
// Any non-supported display types will return null - test classes will need to handle setting the values
return null;
}
}
}

private String getStringValue(Schema.DescribeFieldResult fieldDescribe) {
String strValue = 'Test string for ' + fieldDescribe.getType();
Integer maxLength = fieldDescribe.getLength();

return strValue.length() <= maxLength ? strValue : strValue.left(maxLength);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>54.0</apiVersion>
<status>Active</status>
</ApexClass>
Loading

0 comments on commit bd1c316

Please sign in to comment.