Skip to content

Commit 8387c75

Browse files
authored
Release v6.2.0
Release v6.2.0
2 parents 89220d5 + f5ff5c2 commit 8387c75

File tree

9 files changed

+325
-11
lines changed

9 files changed

+325
-11
lines changed

force-app/main/default/classes/cached-soql/SOQLCache.cls

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Copyright (c) 2025 Beyond The Cloud Sp. z o.o. (BeyondTheCloud.Dev)
33
* Licensed under the MIT License (https://github.com/beyond-the-cloud-dev/soql-lib/blob/main/LICENSE)
44
*
5-
* v6.1.0
5+
* v6.2.0
66
*
77
* PMD False Positives:
88
* - ExcessivePublicCount: It is a library class and exposes all necessary methods to construct a query

force-app/main/default/classes/cached-soql/SOQLCache_Test.cls

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Copyright (c) 2025 Beyond The Cloud Sp. z o.o. (BeyondTheCloud.Dev)
44
* Licensed under the MIT License (https://github.com/beyond-the-cloud-dev/soql-lib/blob/main/LICENSE)
55
*
6-
* v6.1.0
6+
* v6.2.0
77
*
88
* PMD False Positives:
99
* - CyclomaticComplexity: It is a library and we tried to put everything into ONE test class

force-app/main/default/classes/soql-evaluator/SOQLEvaluator.cls

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Copyright (c) 2025 Beyond The Cloud Sp. z o.o. (BeyondTheCloud.Dev)
33
* Licensed under the MIT License (https://github.com/beyond-the-cloud-dev/soql-lib/blob/main/LICENSE)
44
*
5-
* v6.1.0
5+
* v6.2.0
66
*
77
* PMD False Positives:
88
* - CognitiveComplexity: It is a library and we tried to put everything into ONE class

force-app/main/default/classes/soql-evaluator/SOQLEvaluator_Test.cls

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Copyright (c) 2025 Beyond The Cloud Sp. z o.o. (BeyondTheCloud.Dev)
44
* Licensed under the MIT License (https://github.com/beyond-the-cloud-dev/soql-lib/blob/main/LICENSE)
55
*
6-
* v6.1.0
6+
* v6.2.0
77
*
88
* PMD False Positives:
99
* - CyclomaticComplexity: It is a library and we tried to put everything into ONE test class

force-app/main/default/classes/standard-soql/SOQL.cls

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Copyright (c) 2025 Beyond The Cloud Sp. z o.o. (BeyondTheCloud.Dev)
33
* Licensed under the MIT License (https://github.com/beyond-the-cloud-dev/soql-lib/blob/main/LICENSE)
44
*
5-
* v6.1.0
5+
* v6.2.0
66
*
77
* PMD False Positives:
88
* - ExcessivePublicCount: It is a library class and exposes all necessary methods to construct a query
@@ -417,6 +417,9 @@ public virtual inherited sharing class SOQL implements Queryable {
417417
Mockable thenReturn(Map<String, Object> aggregatedResult);
418418
// Count
419419
Mockable thenReturn(Integer count);
420+
// Exception
421+
void throwException();
422+
void throwException(String message);
420423
}
421424

422425
// Backward support - it's going to be removed in the future
@@ -1045,7 +1048,7 @@ public virtual inherited sharing class SOQL implements Queryable {
10451048
this.whereAre(Filter.with(relationshipName, targetKeyField).isNotNull());
10461049
return this.converter.transform(this.executor.toList()).toAggregatedIdMapBy(relationshipName, targetKeyField);
10471050
}
1048-
1051+
10491052
public Map<String, SObject> toMap(SObjectField keyField) {
10501053
this.with(keyField);
10511054
this.whereAre(Filter.with(keyField).isNotNull());
@@ -2402,6 +2405,7 @@ public virtual inherited sharing class SOQL implements Queryable {
24022405
public SObjectMock sObjectMock = new SObjectMock();
24032406
public CountMock countMock = new CountMock();
24042407
public AggregateResultProxies aggregateResultMock = new AggregateResultProxies();
2408+
public ExceptionMock exceptionMock = null;
24052409

24062410
public Mockable thenReturn(List<Map<String, Object>> aggregatedResults) {
24072411
this.aggregateResultMock.add(aggregatedResults);
@@ -2428,6 +2432,19 @@ public virtual inherited sharing class SOQL implements Queryable {
24282432
return this;
24292433
}
24302434

2435+
public void throwException() {
2436+
throwException('List has no rows for assignment to SObject');
2437+
}
2438+
2439+
public void throwException(String message) {
2440+
this.exceptionMock = new ExceptionMock();
2441+
this.exceptionMock.set(new QueryException(message));
2442+
}
2443+
2444+
public Boolean hasExceptionMock() {
2445+
return this.exceptionMock != null;
2446+
}
2447+
24312448
public SoqlMock useLegacyMockingBehavior() {
24322449
this.sObjectMock.useLegacyMockingBehavior = true;
24332450
return this;
@@ -2568,6 +2585,18 @@ public virtual inherited sharing class SOQL implements Queryable {
25682585
}
25692586
}
25702587

2588+
private class ExceptionMock {
2589+
private QueryException queryException;
2590+
2591+
public void set(QueryException queryException) {
2592+
this.queryException = queryException;
2593+
}
2594+
2595+
public QueryException get() {
2596+
return this.queryException;
2597+
}
2598+
}
2599+
25712600
private inherited sharing class Executor {
25722601
private DatabaseQuery sharingExecutor;
25732602
private AccessLevel accessMode;
@@ -2619,6 +2648,7 @@ public virtual inherited sharing class SOQL implements Queryable {
26192648
this.incrementQueryIssued();
26202649

26212650
if (!this.mocks.isEmpty()) {
2651+
this.throwExceptionMockIfExists();
26222652
return this.getMockedListProxy();
26232653
}
26242654

@@ -2632,6 +2662,12 @@ public virtual inherited sharing class SOQL implements Queryable {
26322662
).getRecords();
26332663
}
26342664

2665+
private void throwExceptionMockIfExists() {
2666+
if (this.mocks[0].hasExceptionMock()) {
2667+
throw this.mocks[0].exceptionMock.get();
2668+
}
2669+
}
2670+
26352671
private List<SObject> getMockedListProxy() {
26362672
if (this.mocks.size() == 1) {
26372673
return this.mocks[0].sObjectMock.get(this.builder.fields, this.builder.subQueries);
@@ -2643,6 +2679,7 @@ public virtual inherited sharing class SOQL implements Queryable {
26432679
this.incrementQueryIssued();
26442680

26452681
if (!this.mocks.isEmpty()) {
2682+
this.throwExceptionMockIfExists();
26462683
return this.getMockedAggregateProxy();
26472684
}
26482685

@@ -2660,6 +2697,7 @@ public virtual inherited sharing class SOQL implements Queryable {
26602697
this.incrementQueryIssued();
26612698

26622699
if (!this.mocks.isEmpty()) {
2700+
this.throwExceptionMockIfExists();
26632701
return this.getMockedCount();
26642702
}
26652703

@@ -2930,7 +2968,7 @@ public virtual inherited sharing class SOQL implements Queryable {
29302968
public Map<String, List<String>> toAggregatedMap(SObjectField keyField, String relationshipName, SObjectField targetKeyField) {
29312969
Map<String, List<String>> customValuesPerCustomKey = new Map<String, List<String>>();
29322970
List<String> relationshipPathFields = relationshipName.split('\\.');
2933-
2971+
29342972
for (SObject record : this.recordsToTransform) {
29352973
String key = String.valueOf(record.get(keyField));
29362974
String value = extractNestedFieldValue(record, relationshipPathFields, targetKeyField);
@@ -2978,4 +3016,4 @@ public virtual inherited sharing class SOQL implements Queryable {
29783016
return Id.valueOf(prefix + '0000' + randomPart);
29793017
}
29803018
}
2981-
}
3019+
}

force-app/main/default/classes/standard-soql/SOQL_Test.cls

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Copyright (c) 2025 Beyond The Cloud Sp. z o.o. (BeyondTheCloud.Dev)
44
* Licensed under the MIT License (https://github.com/beyond-the-cloud-dev/soql-lib/blob/main/LICENSE)
55
*
6-
* v6.1.0
6+
* v6.2.0
77
*
88
* PMD False Positives:
99
* - CyclomaticComplexity: It is a library and we tried to put everything into ONE test class
@@ -3522,6 +3522,43 @@ private class SOQL_Test {
35223522
Assert.isNotNull(result.Id, 'The result account Id should be always set even if the mocked account Id is not set.');
35233523
}
35243524

3525+
@IsTest
3526+
static void mockingQueryException() {
3527+
// Test
3528+
SOQL.mock('mockingQuery').throwException();
3529+
3530+
Exception queryException = null;
3531+
3532+
try {
3533+
Account result = (Account) SOQL.of(Account.SObjectType).with(Account.Name).mockId('mockingQuery').toObject();
3534+
} catch (QueryException e) {
3535+
queryException = e;
3536+
}
3537+
3538+
// Verify
3539+
Assert.isNotNull(queryException, 'The query exception should be thrown, because it\'s mocked.');
3540+
Assert.areEqual('List has no rows for assignment to SObject', queryException.getMessage(), 'The query exception should be thrown.');
3541+
}
3542+
3543+
@IsTest
3544+
static void mockingQueryExceptionWithMessage() {
3545+
// Test
3546+
String message = 'No such column \'Name\' on entity \'Account\'.';
3547+
SOQL.mock('mockingQuery').throwException(message);
3548+
3549+
Exception queryException = null;
3550+
3551+
try {
3552+
Account result = (Account) SOQL.of(Account.SObjectType).with(Account.Name).mockId('mockingQuery').toObject();
3553+
} catch (QueryException e) {
3554+
queryException = e;
3555+
}
3556+
3557+
// Verify
3558+
Assert.isNotNull(queryException, 'The query exception should be thrown, because it\'s mocked.');
3559+
Assert.areEqual(message, queryException.getMessage(), 'The query exception should be thrown.');
3560+
}
3561+
35253562
@IsTest
35263563
static void mockingCount() {
35273564
// Test
@@ -4475,7 +4512,7 @@ private class SOQL_Test {
44754512
static void mockedtoAggregatedIdMapByRelationshipField() {
44764513
// Setup
44774514
Id parentId = SOQL.IdGenerator.get(Account.SObjectType);
4478-
4515+
44794516
SOQL.mock('mockingQuery').thenReturn(new List<Account>{
44804517
new Account(Name = 'Account 1', Parent = new Account(Name = 'Parent 1', Id = parentId)),
44814518
new Account(Name = 'Account 2', Parent = new Account(Name = 'Parent 2', Id = parentId))

website/docs/soql/advanced/mocking.md

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ public class ExampleControllerTest {
381381

382382
We are using field aliasing: https://salesforce.stackexchange.com/questions/393308/get-a-list-of-one-column-from-a-soql-result
383383

384-
Its approximately 2x more efficient than a standard for loop. Because of this, mocking works differently for the following methods:
384+
It's approximately 2x more efficient than a standard for loop. Because of this, mocking works differently for the following methods:
385385

386386
- `toIdsOf(SObjectField field)`
387387
- `toIdsOf(String relationshipName, SObjectField field)`
@@ -425,3 +425,92 @@ public class ExampleControllerTest {
425425
}
426426
}
427427
```
428+
429+
## Mocking Exceptions
430+
431+
SOQL Lib supports mocking query exceptions to test error handling scenarios in your code. This is crucial for validating that your application gracefully handles database errors, security violations, and other query-related exceptions.
432+
433+
### Default Exception
434+
435+
The `.throwException()` method simulates a standard query exception with the default message: **"List has no rows for assignment to SObject"**.
436+
437+
This is useful for testing code paths that handle empty query results or unavailable data.
438+
439+
```apex title="ExampleController.cls"
440+
public with sharing class ExampleController {
441+
public static Account getAccountById(Id accountId) {
442+
try {
443+
return (Account) SOQL.of(Account.SObjectType)
444+
.with(Account.Name, Account.BillingCity)
445+
.byId(accountId)
446+
.mockId('ExampleController.getAccountById')
447+
.toObject();
448+
} catch (Exception e) {
449+
// Logger here
450+
throw e;
451+
}
452+
}
453+
}
454+
```
455+
456+
```apex title="ExampleControllerTest.cls"
457+
@IsTest
458+
static void getAccountByIdException() {
459+
SOQL.mock('ExampleController.getAccountById').throwException();
460+
461+
Test.startTest();
462+
Exception error;
463+
try {
464+
Account result = ExampleController.getAccountById('001000000000000AAA');
465+
} catch (Exception e) {
466+
error = e;
467+
}
468+
Test.stopTest();
469+
470+
Assert.isNotNull(error, 'The query exception should be thrown.');
471+
}
472+
```
473+
474+
### Custom Exception Message
475+
476+
Use `.throwException(message)` to simulate a query exception with a custom error message, such as field-level security errors or invalid field references.
477+
478+
```apex title="Controller with Error Handling"
479+
public with sharing class ExampleController {
480+
public static Account getAccountById(Id accountId) {
481+
try {
482+
return (Account) SOQL.of(Account.SObjectType)
483+
.with(Account.Name, Account.BillingCity)
484+
.byId(accountId)
485+
.mockId('ExampleController.getAccountById')
486+
.toObject();
487+
} catch (Exception e) {
488+
// Logger here
489+
throw e;
490+
}
491+
}
492+
}
493+
```
494+
495+
```apex title="Unit Test with Default Exception Mock"
496+
@IsTest
497+
public class ExampleControllerTest {
498+
@IsTest
499+
static void getAccountByIdException() {
500+
String errorMessage = 'No such column \'InvalidField__c\' on entity \'Account\'.';
501+
SOQL.mock('ExampleController.getAccountById').throwException(errorMessage);
502+
503+
Test.startTest();
504+
Exception error;
505+
try {
506+
Account result = ExampleController.getAccountById('001000000000000AAA');
507+
} catch (Exception e) {
508+
error = e;
509+
}
510+
Test.stopTest();
511+
512+
Assert.isNotNull(error, 'The query exception should be thrown.');
513+
}
514+
}
515+
```
516+

0 commit comments

Comments
 (0)