Skip to content

Releases: beyond-the-cloud-dev/soql-lib

v6.4.0

04 Nov 20:26

Choose a tag to compare

v6.4.0 - 04-November-2025

Unlocked package fix.

v6.3.0

04 Nov 20:24

Choose a tag to compare

v6.3.0 - 04-November-2025

Unlocked package fix.

v6.2.0

18 Oct 21:27

Choose a tag to compare

v6.2.0 - 18-October-2024

Scope

  • Enhanced Mocking Capabilities
  • Documentation Update

SOQL

  • Added support for mocking query exceptions
  • Improved error testing capabilities

Mocking Query Exceptions

SOQL Lib now supports mocking query exceptions, enabling you to test error handling scenarios in your Apex code. This is essential for validating that your application gracefully handles database errors, security violations, and other query-related exceptions.

Default Exception Mock

Use .throwException() to simulate a standard query exception with the default message: "List has no rows for assignment to SObject".

Controller

public with sharing class ExampleController {
    public static Account getAccountById(Id accountId) {
        try {
            return (Account) SOQL.of(Account.SObjectType)
                .with(Account.Name, Account.BillingCity)
                .byId(accountId)
                .mockId('ExampleController.getAccountById')
                .toObject();
        } catch (Exception e) {
            // Logger here
            throw e;
        }
    }
}

Test Class

@IsTest
static void getAccountByIdException() {
    SOQL.mock('ExampleController.getAccountById').throwException();

    Test.startTest();
    Exception error;
    try {
        Account result = ExampleController.getAccountById('001000000000000AAA');
    } catch (Exception e) {
        error = e;
    }
    Test.stopTest();

    Assert.isNotNull(error, 'The query exception should be thrown.');
}

Custom Exception Message Mock

Use .throwException(message) to simulate a query exception with a custom error message, such as field-level security errors or invalid field references.

Test Class

@IsTest
static void getAccountByIdException() {
    String errorMessage = 'No such column \'InvalidField__c\' on entity \'Account\'.';
    SOQL.mock('ExampleController.getAccountById').throwException(errorMessage);

    Test.startTest();
    Exception error;
    try {
        Account result = ExampleController.getAccountById('001000000000000AAA');
    } catch (Exception e) {
        error = e;
    }
    Test.stopTest();

    Assert.isNotNull(error, 'The query exception should be thrown.');
    Assert.isTrue(error.getMessage().contains('InvalidField__c'));
}

Documentation Update

Added comprehensive documentation for exception mocking, including:

  • Detailed examples of both default and custom exception messages
  • Best practices for testing error handling scenarios
  • Integration examples with controller error handling patterns

🚨 Breaking Changes 🚨

There are no breaking changes in this release. All new features are additive.

v6.1.0

14 Sep 08:01

Choose a tag to compare

v6.1.0 - September 14, 2025

Scope

  • NEW: Enhanced Result Mapping with ID-based Methods
  • IMPROVED: Automatic Performance Optimization with Null Checking
  • IMPROVED: Documentation Website UI/UX Enhancements
  • IMPROVED: Code Formatting Optimization - Converted Indentation to Tabs

Major New Features

Enhanced Result Mapping - ID-based Methods

The biggest addition to v6.1.0 is the introduction of new ID-focused result mapping methods that provide more precise control over how query results are transformed into maps using ID keys.

New ID Mapping Methods

// Map records by any ID field
Map<Id, Account> ownerIdToAccount = (Map<Id, Account>) SOQL.of(Account.SObjectType)
    .with(Account.Id, Account.Name)
    .toIdMapBy(Account.OwnerId);

// Map records by relationship ID fields  
Map<Id, Account> parentIdToAccount = (Map<Id, Account>) SOQL.of(Account.SObjectType)
    .with(Account.Id, Account.Name)
    .toIdMapBy('Parent', Account.Id);

// Group multiple records by ID field
Map<Id, List<Account>> ownerIdToAccounts = (Map<Id, List<Account>>) SOQL.of(Account.SObjectType)
    .with(Account.Id, Account.Name)
    .toAggregatedIdMapBy(Account.OwnerId);

// Group multiple records by relationship ID field
Map<Id, List<Account>> parentIdToAccounts = (Map<Id, List<Account>>) SOQL.of(Account.SObjectType)
    .with(Account.Id, Account.Name)
    .toAggregatedIdMapBy('Parent', Account.Id);

Automatic Performance Optimization

Smart Null Checking for Better Query Performance

All mapping methods (toMap, toAggregatedMap, toIdMapBy, toAggregatedIdMapBy) now automatically add null-checking conditions to improve query performance:

  • WHERE keyField != null - Automatically added for direct field mapping
  • WHERE relationshipName.targetKeyField != null - Automatically added for relationship field mapping

This optimization reduces result set size and improves query execution time by filtering out records with null key values that would be unusable in the resulting maps.

Traditional Approach:

// Manual null checking required
Map<Id, Account> ownerIdToAccount = new Map<Id, Account>();
for (Account acc : [SELECT Id, Name, OwnerId FROM Account WHERE OwnerId != null]) {
    ownerIdToAccount.put(acc.OwnerId, acc);
}

SOQL Lib Approach:

// Automatic null checking built-in
Map<Id, Account> ownerIdToAccount = (Map<Id, Account>) SOQL.of(Account.SObjectType)
    .with(Account.Id, Account.Name)
    .toIdMapBy(Account.OwnerId);

Documentation and UI Improvements

Enhanced Documentation Website

Light/Dark Mode Compatibility

  • Fixed homepage text visibility issues in light mode
  • Improved contrast and readability across all themes
  • Enhanced user experience with responsive color schemes

Comprehensive Method Documentation

  • Added complete documentation for all new ID mapping methods
  • Included practical examples showing traditional vs SOQL Lib approaches
  • Added performance optimization notes for all mapping methods
  • Enhanced code examples with field selection for better clarity

Code Formatting Optimization

Indentation Converted to Tabs

  • Converted all code indentation from spaces to tabs across the entire codebase
  • Reduced character count by approximately 10% improving file size and load times

New Methods Reference

SOQL Result Methods

toIdMapBy(SObjectField field)

Creates a map where the key is the Id extracted from the specified field and the value is the SObject record.

toIdMapBy(String relationshipName, SObjectField targetKeyField)

Creates a map where the key is the Id extracted from the specified relationship field and the value is the SObject record.

toAggregatedIdMapBy(SObjectField keyField)

Creates a map where the key is the Id extracted from the specified field and the value is a list of SObject records grouped by that Id.

toAggregatedIdMapBy(String relationshipName, SObjectField targetKeyField)

Creates a map where the key is the Id extracted from the specified relationship field and the value is a list of SObject records grouped by that Id.

Contributors

Thank you @kaboomkazoom for your ideas and support!


Download: v6.1.0 Release

Documentation: https://soql.beyondthecloud.dev

GitHub: https://github.com/beyond-the-cloud-dev/soql-lib

v6.0.2

30 Aug 13:08
4c53551

Choose a tag to compare

Release Notes - Fix SOQL Cache key

SOQLCache.cls

When a custom object with a __c or __mdt suffix was cached using the SOQLCache module, the following error would occur:
Invalid Key, Key cannot be null or empty and must be alphanumeric.

This patch resolves the issue.

v6.0.1

27 Aug 20:21
fd856ee

Choose a tag to compare

Release Notes - Fix toIdsOf/toValuesOf Mocking

SOQL_Test.cls

Added comprehensive test methods for mocking scenarios:

  • toIdWithMocking(): Tests single Id extraction with mocked data
  • toIdsWithMocking(): Tests multiple Ids extraction with mocked data
  • toIdsOfWithMocking(): Tests field aliasing for Id extraction with AggregateResult mocking
  • toIdsOfRelationshipFieldWithMocking(): Tests relationship field Id extraction with mocking

Key Testing Pattern:

SOQL.mock('mockingQuery').thenReturn(
    (List<AggregateResult>) JSON.deserialize(
        JSON.serialize(new List<Map<String, Object>>{
            new Map<String, Object>{ 'Id' => SOQL.IdGenerator.get(Account.SObjectType) }
        }),
        List<AggregateResult>.class
    )
);

What This Fixes

Before the Fix

// This would fail with mocking
Set<Id> parentIds = SOQL.of(Account.SObjectType)
    .mockId('test')
    .toIdsOf(Account.ParentId);

After the Fix

// Setup mock with AggregateResult structure
SOQL.mock('test').thenReturn(
    (List<AggregateResult>) JSON.deserialize(
        JSON.serialize(new List<Map<String, Object>>{
            new Map<String, Object>{ 'Id' => someAccountId }
        }),
        List<AggregateResult>.class
    )
);

// Now works properly
Set<Id> parentIds = SOQL.of(Account.SObjectType)
    .mockId('test')
    .toIdsOf(Account.ParentId);

v6.0.0

24 Aug 11:58
0b220aa

Choose a tag to compare

v6.0.0 - August 24, 2025

Scope

  • NEW: SOQL Evaluator for Static SOQL Processing
  • Enhanced SOQLCache with Relationship Field Support
  • Comprehensive Documentation Overhaul
  • Multiple Condition Support for Cached Queries
  • Improved Testing and Code Coverage

SOQLEvaluator

  • NEW: Introduced SOQLEvaluator for processing static query results
  • NEW: Complete mocking framework for static data evaluation
  • NEW: Field-level security support for static data
  • NEW: All standard SOQL Lib result methods (toId, toList, toMap, etc.)

SOQLCache

  • NEW: Added relationship field support with with(relationshipName, field) methods
  • NEW: Added toIdOf() method for extracting IDs from specific fields
  • NEW: Enhanced filtering flexibility with allowFilteringByNonUniqueFields() and allowQueryWithoutConditions()
  • NEW: Support for multiple conditions and additional allowed condition fields
  • IMPROVED: Better error handling and query validation

Major New Features

SOQL Evaluator - Static Data Processing

The biggest addition to v6.0.0 is the new SOQLEvaluator class, which brings SOQL Lib's powerful API to static query results. This enables you to apply the same transformation and filtering patterns to in-memory data that you use for database queries.

Basic Usage

List<Account> processedAccounts = SOQLEvaluator.of([SELECT Id, Name FROM Account])
    .stripInaccessible()
    .toList();

// Extract data using familiar methods
Set<Id> ownerIds = SOQLEvaluator.of([SELECT OwnerId FROM Account])
    .toIdsOf(Account.OwnerId);

Map<Id, Account> accountMap = SOQLEvaluator.of([SELECT Id, Name FROM Account])
    .toMap();

Field-Level Security Support

List<Task> safeTasks = SOQLEvaluator.of([SELECT Id, Type FROM Task])
    .stripInaccessible(AccessType.READABLE)
    .toList();

Comprehensive Mocking for Tests

// In your controller
public static List<Account> getAccounts() {
    return SOQLEvaluator.of([SELECT Id, Name FROM Account])
        .mockId('ExampleController.getAccounts')
        .toList();
}

// In your test
@IsTest
static void testProcessExternalAccounts() {
    List<Account> mockAccounts = new List<Account>{
        new Account(Name = 'Test Account 1'),
        new Account(Name = 'Test Account 2')
    };
    
    SOQLEvaluator.mock('ExampleController.getAccounts')
        .thenReturn(mockAccounts);
    
    Test.startTest();
    List<Account> result = ExampleController.getAccounts();
    Test.stopTest();
    
    Assert.areEqual(2, result.size());
}

Enhanced SOQLCache with Relationship Field Support

SOQLCache now supports querying relationship fields directly, making it easier to work with related data in cached queries.

New Relationship Field Methods

// Single relationship field
SOQLCache.of(Account.SObjectType)
    .with(Account.Name)
    .with('Owner', User.Name)
    .byId(accountId)
    .toObject();

// Multiple relationship fields
SOQLCache.of(Account.SObjectType)
    .with('Owner', User.Name, User.Email, User.Department)
    .byId(accountId)
    .toObject();

// Using field collections
SOQLCache.of(Account.SObjectType)
    .with('Owner', new List<SObjectField>{ User.Name, User.Email })
    .byId(accountId)
    .toObject();

New toIdOf() Method for SOQLCache

// Extract specific field IDs from cached data
Id ownerId = SOQLCache.of(Account.SObjectType)
    .byId(accountId)
    .toIdOf(Account.OwnerId);

Enhanced Cache Configuration

Flexible Filtering Options

// Allow filtering by non-unique fields (use with caution)
SOQLCache.of(Account.SObjectType)
    .allowFilteringByNonUniqueFields()
    .whereEqual(Account.Industry, 'Technology')
    .toList();

// Allow queries without WHERE conditions
SOQLCache.of(Account.SObjectType)
    .allowQueryWithoutConditions()
    .toList();

Additional Allowed Condition Fields

public class SOQL_Account extends SOQLCache implements SOQLCache.Selector {
    public override List<SObjectField> additionalAllowedConditionFields() {
        return new List<SObjectField>{ 
            Account.Industry, 
            Account.Type 
        };
    }
}

Documentation Revolution

We've completely overhauled our documentation to provide the best developer experience possible:

Comprehensive Code Examples

  • Titled Code Blocks: Every code example now has descriptive titles for better context
  • Real-World Scenarios: Added extensive examples covering complex business cases
  • Side-by-Side Comparisons: Traditional SOQL vs SOQL Lib approaches clearly demonstrated

Breaking Changes

Encapsulation improvements

Class: SOQL.cls

.of methods will now return the SOQL.Queryable interface instead of the SOQL class itself.
If this is a breaking change for you, retain the previous approach.

Previous:

public static SOQL of(SObjectType ofObject) {
   return new SOQL(ofObject);
}

public static SOQL of(String ofObject) {
   return new SOQL(ofObject);
}

New:

public static Queryable of(SObjectType ofObject) {
   return new SOQL(ofObject);
}

public static Queryable of(String ofObject) {
   return new SOQL(ofObject);
}

Download: v6.0.0 Release

Documentation: https://soql.beyondthecloud.dev

GitHub: https://github.com/beyond-the-cloud-dev/soql-lib

v5.2.0

13 Aug 19:59

Choose a tag to compare

13-August-2025

Scope

  • New Query Result Methods
  • Enhanced Mocking System (SOQL & SOQLCache)
  • Documentation Improvements
  • Performance Optimizations
  • Code Quality Improvements

SOQL

  • Added new result extraction methods: toIds(), toIdsOf(), and nullsOrder().
  • Enhanced subquery support with relationship name tracking.
  • Improved mocking system with sequential stack functionality.
  • Performance optimizations in string building and field handling.
  • Better mock field stripping for subqueries and plain fields.

SOQLCache

  • Enhanced mocking system with stack functionality supporting sequential mocks.
  • Improved performance with optimized object instantiation and field conversion.
  • Added null safety improvements and better error handling.
  • Updated to API version 64.0 across all components.

Documentation

  • Added comprehensive "Minimalistic Selectors" section with design philosophy and examples.
  • Enhanced "Minimal Fields" documentation with performance benefits and best practices.
  • Improved mocking documentation with stack functionality explanation.
  • Added practical before/after examples for new query result methods.

API Version Update

All Apex classes have been updated to API version 64.0, ensuring compatibility with the latest Salesforce features and improvements.

Enhanced Mocking System

Both SOQL and SOQLCache now feature improved mocking capabilities with better performance and cleaner code organization.

SOQL Library Improvements

The standard SOQL library has received several mocking and performance enhancements:

Legacy Mock Consolidation

// Before: Duplicate code in deprecated methods
private static void setMock(String mockId, SObject record) {
    SOQL.queryIdToMock.put(mockId, new List<SoqlMock>{ new SoqlMock().useLegacyMockingBehavior() });
    SOQL.queryIdToMock.get(mockId).get(SOQL.queryIdToMock.get(mockId).size() - 1).thenReturn(record);
}

// After: Clean helper method
private static void setMock(String mockId, SObject record) {
    SOQL.createLegacyMock(mockId).thenReturn(record);
}

private static SoqlMock createLegacyMock(String mockId) {
    SoqlMock mock = new SoqlMock().useLegacyMockingBehavior();
    SOQL.queryIdToMock.put(mockId, new List<SoqlMock>{ mock });
    return mock;
}

Enhanced Binder with Reset Capability

public class Binder {
    public void reset() {
        this.bindIndex = 0;
        this.binding.clear();
    }
}

// Usage in toString():
public override String toString() {
    binder.reset(); // Better memory management
    return this.builder.toString();
}

SOQLCache Enhancements

SOQLCache now supports the same sophisticated mocking capabilities as the standard SOQL library, including sequential mock execution.

Sequential Mock Stack

You can now set up multiple mocks for the same query identifier, which will be consumed in FIFO (First In, First Out) order:

Test Setup

@IsTest
static void mockStack() {
    // Setup multiple sequential mocks
    SOQL.mock('mockingQuery').thenReturn(new Profile(Name = 'Test 1'));
    SOQL.mock('mockingQuery').thenReturn(new Profile(Name = 'Test 2'));
    SOQL.mock('mockingQuery').thenReturn(new Profile(Name = 'Test 3'));

    // Test
    SOQLCache.Cacheable query = SOQLCache.of(Profile.SObjectType)
        .mockId('mockingQuery')
        .whereEqual(Profile.Name, 'System Administrator');

    Profile profile1 = (Profile) query.toObject(); // Returns 'Test 1'
    Profile profile2 = (Profile) query.toObject(); // Returns 'Test 2'  
    Profile profile3 = (Profile) query.toObject(); // Returns 'Test 3'
    Profile profile4 = (Profile) query.toObject(); // Returns 'Test 3' (reuses last)
}

Modernized Mock API

The mocking system has been updated to use the modern .mock().thenReturn() pattern instead of the deprecated .setMock() methods:

Before

SOQL.setMock('ProfileQuery', mockProfile);

After

SOQL.mock('ProfileQuery').thenReturn(mockProfile);

Performance Optimizations

SOQL Library Performance Improvements

Optimized String Building
The toString() method now uses efficient String.join() instead of string concatenation:

// Before: String concatenation in loop
public override String toString() {
    String query = '';
    for (QueryClause clause : this.clauses) {
        if (clause != null) {
            query += ' ' + clause.toString(); // Inefficient O(n²) complexity
        }
    }
    return query.trim();
}

// After: Efficient list-based joining
public override String toString() {
    List<String> queryParts = new List<String>();
    for (QueryClause clause : this.clauses) {
        if (clause != null) {
            queryParts.add(clause.toString());
        }
    }
    return String.join(queryParts, ' '); // O(n) complexity
}

Enhanced Field Handling

  • Removed unnecessary null coalescing operations in field processing
  • Optimized doExist() method with setLimit(1) for better performance
  • Improved isEmpty() checks instead of size() > 0 comparisons

SOQLCache Performance Improvements

Reduced Object Instantiation

Optimized cache storage operations to reuse proxy objects instead of creating multiple instances:

Before

new CacheStorageProxy(ofObject).apexTransaction().removeRecordsFromCache(records);
new CacheStorageProxy(ofObject).orgCache().removeRecordsFromCache(records);
new CacheStorageProxy(ofObject).sessionCache().removeRecordsFromCache(records);

After

CacheStorageProxy proxy = new CacheStorageProxy(ofObject);
proxy.apexTransaction().removeRecordsFromCache(records);
proxy.orgCache().removeRecordsFromCache(records);
proxy.sessionCache().removeRecordsFromCache(records);

Improved Field Conversion

Standardized field-to-string conversion throughout the codebase using .toString() instead of string concatenation:

// Consistent field conversion
this.cachedFields.add(field.toString());
return this.whereEqual(field.toString(), value);

Code Quality Improvements

Enhanced Null Safety

Added proper null safety checks to prevent NullPointerExceptions:

public Id toId() {
    return this.toObject()?.Id; // Safe null handling
}

Optimized Boolean Logic

Improved performance in field validation with early return patterns:

Before

public Boolean hasAllRequestedFields(SObject record) {
    Boolean hasCachedFields = true;
    for (String field : this.cachedFields) {
        hasCachedFields &= record.isSet(field);
    }
    return hasCachedFields;
}

After

public Boolean hasAllRequestedFields(SObject record) {
    for (String field : this.cachedFields) {
        if (!record.isSet(field)) {
            return false; // Early exit for better performance
        }
    }
    return true;
}

Cleanup and Optimization

  • Removed unused variables and redundant code
  • Optimized time calculations with direct numeric constants
  • Improved error handling consistency
  • Enhanced executor logic for better mock management

Breaking Changes

There are no breaking changes in this release. All optimizations maintain backward compatibility while improving performance and reliability.

Contributors

Thank you to all contributors who helped make this release possible through code optimization, testing, and feedback.

v5.1.0

06 Jun 21:29

Choose a tag to compare

07-June-2025

Scope

  • Documentation Update
  • New SOQL Features
  • Enhanced AggregateResult Mocking
  • Querying Improvements

SOQL

  • Added support for WITH DATA CATEGORY.
  • Introduced a new way to mock AggregateResult.
  • Added orderByCount() for aggregate queries.
  • Enhanced toValuesOf() to support related fields.
  • Added alwaysAddId() for convenience.

Documentation Update

We've invested heavily in making our documentation clearer and more comprehensive.

  • New Examples: Added extensive examples for new features like WITH DATA CATEGORY and AggregateResult mocking.
  • Titled Code Blocks: All code examples now have titles, making it easier to understand the context of each snippet.
  • Improved Structure: The documentation sidebar has been reorganized for better navigation.

New Feature: WITH DATA CATEGORY

SOQL Lib now fully supports WITH DATA CATEGORY clauses, allowing you to filter queries based on Salesforce Knowledge data categories.

SOQL

SELECT Id, Title
FROM Knowledge__kav
WITH DATA CATEGORY Geography__c AT (Europe__c, North_America__c)

SOQL Lib

SOQL.of(Knowledge__kav.SObjectType)
    .with(Knowledge__kav.Id, Knowledge__kav.Title)
    .withDataCategory(
        SOQL.DataCategoryFilter
            .with('Geography__c')
            .at(new List<String>{ 'Europe__c', 'North_America__c' })
    ).toList();

Enhanced AggregateResult Mocking

Mocking AggregateResult has always been a challenge in Apex. We've introduced SOQL.AggregateResultProxy to simplify this process. Use the new .toAggregatedProxy() method to get mockable results from your aggregate queries.

Controller

public with sharing class ExampleController {
    public void getLeadAggregateResults() {
        List<SOQL.AggregateResultProxy> result = SOQL.of(Lead.SObjectType)
            .with(Lead.LeadSource)
            .COUNT(Lead.Id, 'total')
            .groupBy(Lead.LeadSource)
            .mockId('ExampleController.getLeadAggregateResults')
            .toAggregatedProxy(); // <== Use toAggregatedProxy()
    }
}

Test Class

@IsTest
static void getLeadAggregateResults() {
    List<Map<String, Object>> aggregateResults = new List<Map<String, Object>>{
        new Map<String, Object>{ 'LeadSource' => 'Web',  'total' => 10},
        new Map<String, Object>{ 'LeadSource' => 'Phone', 'total' => 5}
    };

    SOQL.mock('ExampleController.getLeadAggregateResults').thenReturn(aggregateResults);

    Test.startTest();
    List<SOQL.AggregateResultProxy> result = ExampleController.getLeadAggregateResults();
    Test.stopTest();

    Assert.areEqual(2, result.size());
}

Querying Improvements

ORDER BY COUNT()

You can now order grouped results by the count of a field using the orderByCount() method.

SOQL

SELECT Industry
FROM Account
GROUP BY Industry
ORDER BY COUNT(Industry) DESC NULLS LAST

SOQL Lib

SOQL.of(Account.SObjectType)
    .with(Account.Industry)
    .groupBy(Account.Industry)
    .orderByCount(Account.Industry).sortDesc().nullsLast()
    .toAggregated();

toValuesOf() for Related Fields

The toValuesOf() method now supports retrieving values from related parent objects, simplifying data extraction.

Apex

Set<String> parentAccountNames = new Set<String>();
for (Account acc : [SELECT Parent.Name FROM Account WHERE ParentId != NULL]) {
    parentAccountNames.add(acc.Parent.Name);
}

SOQL Lib

Set<String> parentAccountNames = SOQL.of(Account.SObjectType)
    .toValuesOf('Parent', Account.Name);

Breaking Changes

There are no breaking changes in this release.

Contributors

Thank you @packocz for your ideas and support!

v5.0.0

08 Apr 08:18

Choose a tag to compare

08-April-2025

Scope

  • Documentation Update

SOQL and SOQL Cache

  • Better mocking with Mockable interface
  • Code performance improvement
  • Better encapsulation
  • Code refactoring

Documentation Update

We significantly improved SOQL Lib documentation: https://soql.beyondthecloud.dev/

You can find many use cases at https://soql.beyondthecloud.dev/examples/showcase.

The concept is straightforward—each example is shown in SOQL and its equivalent in SOQL Lib.

Example

SOQL

SELECT Id, Name, BillingCity
FROM Account

SOQL Lib

SOQL.of(Account.SObject)
    .with(Account.Id, Account.Name, Account.BillingCity)
    .toList();

Better Mocking

We introduced a new Mockable interface. This interface lays the foundation for future improvements in mocking within SOQL Lib.

public interface Mockable {
   // SObject
   Mockable thenReturn(SObject record);
   Mockable thenReturn(List<SObject> records);
   // Count
   Mockable thenReturn(Integer count);
}
List<Account> accounts = new List<Account>{
    new Account(Name = 'MyAccount 1'),
    new Account(Name = 'MyAccount 2')
};

SOQL.mock('ExampleController.getPartnerAccounts')
    .thenReturn(accounts);

Stripping Extra Fields

We also introduced logic to strip unused fields during mocking.

Problem:
Previously, mocking returned all fields in the mock object, even if those fields weren't part of the actual query. This caused unit tests to pass, but led to runtime errors like:
System.SObjectException: SObject row was retrieved via SOQL without querying the requested field.

Solution:
We now keep only the fields that are actually queried. Extra fields are stripped from mocked records. Currently, this applies only to plain fields like Id, Name, etc. Support for relationship fields and subqueries is planned for future release.

for (Account account : SOQL.of(Account.SObjectType).with(Account.Name).toList()) {
     String industry = account.Industry; // <- field not queried
}
List<Account> accounts = new List<Account>{
    new Account(Name = 'MyAccount 1', Industry = 'IT'),
    new Account(Name = 'MyAccount 2', Industry = 'Administration')
};

SOQL.mock('ExampleController.getPartnerAccounts')
    .thenReturn(accounts);

Better Encapsulation

Previously, the method returned an instance of SOQL instead of the Queryable interface.

New Behavior:
This ensures that callers can only access the interface’s methods - supporting loose coupling, better encapsulation, and adherence to the Dependency Inversion Principle.

// ❌ Before
public SOQL with(SObjectField field) {
    // ...
}

// ✅ After
public Queryable with(SObjectField field) {
    // ...
}

Code Refactoring

SObjectField Optimization

Instead of using:

field.getDescribe().getName()

We now use:

field.toString()

This change makes operations with SObjectField approximately 9x faster.

General Refactoring

We’ve streamlined the code in the SOQL class, resulting in a shorter and more maintainable implementation.

🚨 Breaking Changes 🚨

Mocking

Old mocking methods are still supported for backward compatibility. However, we recommend using the new approach with SOQL.mock(...).thenReturn(...);.

// Backward support

@TestVisible // deprecated
private static void setMock(String mockId, SObject record) {
    mock(mockId).thenReturn(record);
}

@TestVisible // deprecated
private static void setMock(String mockId, List<SObject> records) {
    mock(mockId).thenReturn(records);
}

@TestVisible // deprecated
private static void setCountMock(String mockId, Integer amount) {
    mock(mockId).thenReturn(amount);
}

Queryable

As mentioned in the Better Encapsulation section, all SOQL methods now return the Queryable interface instead of the SOQL class directly.

// ❌ Before
public SOQL with(SObjectField field) {
    // ...
}

// ✅ After
public Queryable with(SObjectField field) {
    // ...
}

If you have existing logic like this:

SOQL query = SOQL.of(...);

Replace it with:

SOQL.Queryable query = SOQL.of(...);