Basic Features
Dynamic SOQL
The SOQL.cls
class provides methods for building SOQL clauses dynamically.
// SELECT Id FROM Account LIMIT 100
SOQL.of(Account.SObjectType)
.with(Account.Id, Account.Name)
.setLimit(100)
.toList();
Automatic binding
All variables used in the WHERE
condition are automatically binded.
// SELECT Id, Name FROM Account WHERE Name = :v1
SOQL.of(Account.SObjectType)
.with(Account.Id, Account.Name)
.whereAre(SOQL.Filter.with(Account.Name).contains('Test'))
.toList();
// Binding Map
{
"v1" : "%Test%"
}
Control FLS
Object permissions and field-level security are controlled by the lib. Developers can change FLS settings to match business requirements.
User mode
By default, all queries are executed in AccessLevel.USER_MODE
.
The object permissions, field-level security, and sharing rules of the current user are enforced.
System mode
Developers can change the mode to AccessLevel.SYSTEM_MODE
by using the .systemMode()
method.
The object and field-level permissions of the current user are ignored, and the record sharing rules are controlled by the sharingMode.
// SELECT Id FROM Account - skip FLS
SOQL.of(Account.SObjectType)
.with(Account.Id, Account.Name)
.systemMode()
.toList();
Control Sharings
Use the with sharing or without sharing keywords on a class to specify whether sharing rules must be enforced. Use the inherited sharing keyword on a class to run the class in the sharing mode of the class that called it.
with sharing
By default, all queries are executed with sharing
, enforced by AccessLevel.USER_MODE
.
AccessLevel.USER_MODE
enforces object permissions and field-level security.
Developer can skip FLS by adding .systemMode()
and .withSharing()
.
// Query executed in without sharing
SOQL.of(Account.SObjectType)
.with(Account.Id, Account.Name)
.systemMode()
.withSharing()
.toList();
without sharing
Developer can control sharing rules by adding .systemMode()
(record sharing rules are controlled by the sharingMode) and .withoutSharing()
.
// Query executed in with sharing
SOQL.of(Account.SObjectType)
.with(Account.Id, Account.Name)
.systemMode()
.withoutSharing()
.toList();
inherited sharing
Developer can control sharing rules by adding .systemMode()
(record sharing rules are controlled by the sharingMode) by default it is inherited sharing
.
// Query executed in inherited sharing
SOQL.of(Account.SObjectType)
.with(Account.Id, Account.Name)
.systemMode()
.toList();
Mocking
Mocking provides a way to substitute records from a Database with some prepared data. Data can be prepared in form of SObject records and lists in Apex code or Static Resource .csv
file.
Mocked queries won't make any SOQL's and simply return data set in method definition, mock will ignore all filters and relations, what is returned depends solely on data provided to the method. Mocking is working only during test execution. To mock SOQL query, use .mockId(id)
method to make it identifiable. If you mark more than one query with the same ID, all marked queries will return the same data.
public with sharing class ExampleController {
public static List<Account> getPartnerAccounts(String accountName) {
return SOQL_Account.query()
.with(Account.BillingCity, Account.BillingCountry)
.whereAre(SOQL.FilterGroup
.add(SOQL.Filter.name().contains(accountName))
.add(SOQL.Filter.recordType().equal('Partner'))
)
.mockId('ExampleController.getPartnerAccounts')
.toList();
}
}
Then in test simply pass data you want to get from Selector to SOQL.setMock(id, data)
method. Acceptable formats are: List<SObject>
or SObject
. Then during execution Selector will return desired data.
List of records
@IsTest
private class ExampleControllerTest {
@IsTest
static void getPartnerAccounts() {
List<Account> accounts = new List<Account>{
new Account(Name = 'MyAccount 1'),
new Account(Name = 'MyAccount 2')
};
SOQL.setMock('ExampleController.getPartnerAccounts', accounts);
// Test
List<Account> result = ExampleController.getPartnerAccounts('MyAccount');
Assert.areEqual(accounts, result);
}
}
Single record
@IsTest
private class ExampleControllerTest {
@IsTest
static void getPartnerAccount() {
SOQL.setMock('ExampleController.getPartnerAccount', new Account(Name = 'MyAccount 1'));
// Test
Account result = (Account) ExampleController.getPartnerAccounts('MyAccount');
Assert.areEqual('MyAccount 1', result.Name);
}
}
Static resource
@IsTest
private class ExampleControllerTest {
@IsTest
static void getPartnerAccounts() {
SOQL.setMock('ExampleController.getPartnerAccounts', Test.loadData(Account.SObjectType, 'ProjectAccounts'));
// Test
List<Account> result = ExampleController.getPartnerAccounts('MyAccount');
Assert.areEqual(5, result.size());
}
}
Count Result
@IsTest
private class ExampleControllerTest {
@IsTest
static void getPartnerAccountsCount() {
SOQL.setCountMock('mockingQuery', 2);
Integer result = SOQL.of(Account.sObjectType).count().mockId('mockingQuery').toInteger();
Assert.areEqual(2, result);
}
}
Avoid duplicates
Generic SOQLs can be keep in selector class.
public inherited sharing class SOQL_Account extends SOQL implements SOQL.Selector {
public static SOQL_Account query() {
return new SOQL_Account();
}
private SOQL_Account() {
super(Account.SObjectType);
// default settings
with(Account.Id, Account.Name, Account.Type)
.systemMode()
.withoutSharing();
}
public SOQL_Account byRecordType(String rt) {
whereAre(Filter.recordType().equal(rt));
return this;
}
public SOQL_Account byIndustry(String industry) {
with(Account.Industry)
.whereAre(Filter.with(Account.Industry).equal(industry));
return this;
}
public SOQL_Account byParentId(Id parentId) {
with(Account.ParentId)
.whereAre(Filter.with(Account.ParentId).equal(parentId));
return this;
}
public String toIndustry(Id accountId) {
return (String) byId(accountId).toValueOf(Account.Industry);
}
}
Default configuration
The selector class can provide default SOQL configuration like default fields, FLS settings, and sharing rules that will be applied to all queries.
public inherited sharing class SOQL_Account extends SOQL implements SOQL.Selector {
public static SOQL_Account query() {
return new SOQL_Account();
}
private SOQL_Account() {
super(Account.SObjectType);
// default configuration
with(Account.Id, Account.Name, Account.Type)
.systemMode()
.withoutSharing();
}
}
Dynamic conditions
Build your conditions in a dynamic way.
Ignore condition
Ignore condition when logic expression evaluate to true.
// SELECT Id FROM Account WHERE BillingCity = 'Krakow'
String accountName = '';
SOQL.of(Account.SObjectType)
.whereAre(SOQL.FilterGroup
.add(SOQL.Filter.with(Account.BillingCity).equal('Krakow'))
.add(SOQL.Filter.name().contains(accountName).ignoreWhen(String.isEmpty(accountName)))
).toList();
Filter Group
Create SOQL.FilterGroup and assign conditions dynamically based on your own criteria.
public List<Account> getAccounts() {
SOQL.FilterGroup filterGroup;
if (UserInfo.getUserType() == 'PowerPartner')
filterGroup = SOQL.FilterGroup
.add(SOQL.Filter.with(Account.Name).equal('Test'));
.add(SOQL.Filter.with(Account.BillingCity).equal('Krakow'));
} else {
filterGroup = SOQL.FilterGroup
.add(SOQL.Filter.with(Account.Name).equal('Other Test'));
}
return SOQL.of(Account.SObjectType)
.whereAre(filterGroup)
.toList();
}