Skip to content

Instantly share code, notes, and snippets.

@SpenceDiNicolantonio
Last active December 20, 2023 19:29
Show Gist options
  • Save SpenceDiNicolantonio/c0fd1af9b2edbac3fdf3799b36a8bff7 to your computer and use it in GitHub Desktop.
Save SpenceDiNicolantonio/c0fd1af9b2edbac3fdf3799b36a8bff7 to your computer and use it in GitHub Desktop.
[Salesforce ID generator] Generates 15-character Salesforce IDs for testing #salesforce #apex
/**
* A collection of static utility methods for unit testing.
*/
@IsTest
public class TestUtil {
// ID generator configuration
private static final Integer ID_RESERVED_CHARACTERS = 7;
private static final Integer ID_INCREMENTER_PREFIX_LENGTH = 1;
private static final String BASE62_ALPHABET = '0123456789abcdefghijklmnopqrstuvwqyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
// Count of generated IDs, used as an incrementer in generateId()
private static Integer idCount = 1;
/**
* Generates a Base62 string that can be used to simulate a Salesforce ID.
* @return A 15-character Base62 string
*/
public static Id generateId() {
// Salesforce IDs are 15-character Base62 strings. The first 7 characters will be the same for all IDs in the
// same environment, while the last 8 characters will be an incremented identifier
// (https://salesforce.stackexchange.com/questions/1653/what-are-salesforce-ids-composed-of)
//
// We'll use the current user's ID to determine the appropriate environment-specific characters. We'll set a
// number of characters following the 7th to 'Z', to make collisions with system-generated IDs unlikely. We'll
// then use a simple incrementing pattern to generate the remaining characters.
// Get environment-specific ID prefix from user ID
String envPrefix = UserInfo.getUserId().substring(0, ID_RESERVED_CHARACTERS);
String incPrefix = ''.rightPad(ID_INCREMENTER_PREFIX_LENGTH, BASE62_ALPHABET.substring(BASE62_ALPHABET.length() - 1));
// Generate incremented value
Integer incrementLength = (15 - ID_RESERVED_CHARACTERS - ID_INCREMENTER_PREFIX_LENGTH);
String increment = convertBase(idCount, 62, BASE62_ALPHABET).leftPad(incrementLength, '0');
// Increment counter and build ID
idCount++;
return envPrefix + incPrefix + increment;
}
/**
* Converts an integer value to a different numerical base, using a provided alphabet for representation.
* @param value Value to convert
* @param radix Numerical base to convert to
* @param alphabet Sequential set of characters defining the alphabet used to represent the resulting value
* @return The converted value
*/
private static String convertBase(Integer value, Integer radix, String alphabet) {
if (radix > alphabet.length()) {
throw new AlphabetLengthException('Length of provided alphabet must be greater or equal to radix');
}
// Short circuit in case of 0
if (value == 0) {
return '0';
}
// Convert input number to given base by repeatedly dividing it by base and taking remainder
String output = '';
while (value > 0) {
Integer charIndex = Math.mod(value, radix);
output = alphabet.substring(charIndex, charIndex + 1) + output;
System.debug('output: ' + output);
value = value / radix;
}
return output;
}
//==================================================================================================================
// Exceptions
//==================================================================================================================
public class AlphabetLengthException extends Exception {}
}
@IsTest
public class TestUtilTest {
@IsTest
private static void shouldGenerateIds() {
Test.startTest();
List<Id> ids = new List<Id>();
for (Integer i = 0; i < 400; i++) {
Id idValue = TestUtil.generateId();
ids.add(idValue);
}
Test.stopTest();
String userIdPrefix = UserInfo.getUserId().substring(0, TestUtil.RESERVED_ID_CHARACTERS);
String incrementPrefix = ''.rightPad(TestUtil.ID_INCREMENT_PREFIX_CHARACTERS, 'Z');
for (Integer i = 0; i < ids.size(); i++) {
String idString = String.valueOf(ids[i]);
// Prefix should match user ID
System.assert(
idString.startsWith(userIdPrefix),
'Expected first ' + TestUtil.RESERVED_ID_CHARACTERS + ' characters of ID to match user ID: ' + idString
);
// Incrementing value should start with prefix
System.assert(
idString.substring(TestUtil.RESERVED_ID_CHARACTERS).startsWith(incrementPrefix),
'Expected ID to contain \'' + incrementPrefix + '\': ' + idString
);
// last characters should increment in Base62
Integer char1Index = Math.Mod(i, 62);
String char1 = TestUtil.BASE62_ALPHABET.substring(char1Index, char1Index + 1);
Integer char2Index = Math.Mod(i/62, 62);
String char2 = TestUtil.BASE62_ALPHABET.substring(char2Index, char2Index + 1);
Integer char3Index = Math.Mod(i/62/62, 62);
String char3 = TestUtil.BASE62_ALPHABET.substring(char3Index, char3Index + 1);
System.assert(
idString.substring(0, 15).endsWith(char3 + char2 + char1),
'Expected ID to end with \'' + char3 + char2 + char1 + '\': ' + idString
);
}
}
}
@Skydaddle
Copy link

private static final String BASE62_ALPHABET = '0123456789abcdefghijklmnopqrstuvwqyzABCDEFGHIJKLMNOPQRSTUVWXYZ';

The BASE62_ALPHABET string has an extra lower case 'q' instead of an 'x'.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment