Complete False Positive Detection Guide

Apex Stem Docs
ApexApexEloquentTestingMockEntryAdvancedBest PracticesSOQL
Comprehensive guide to ApexEloquent's MockEntry false positive detection system with detailed examples, real-world use cases, and advanced testing patterns.

This comprehensive guide covers ApexEloquent's MockEntry false positive detection system with detailed examples, real-world use cases, and advanced testing patterns.

The Core Problem

Traditional Salesforce testing relies on TestDataFactory and database inserts. When developers move to mock-based testing for performance benefits, they create a dangerous gap: mock tests pass when production fails.

This happens because mock objects in tests have all fields populated in memory, while production SOQL queries may miss required fields. ApexEloquent's MockEntry closes this gap by validating field access against your actual query structure, ensuring mocks behave like real SOQL results.

🧪 Use Case 1: Detecting Omissions on the Main Object

Let's see a real-world example with a service class that forgets to select the Name field:

The Service Class (with a bug)

APEX
public with sharing class OppNameUpdater {
    private final Id oppId;
    private final IEloquent eloquent;
 
    public OppNameUpdater(Id oppId, IEloquent eloquent) {
        this.oppId = oppId;
        this.eloquent = eloquent;
    }
 
    public Opportunity execute() {
        Scribe scribe = Scribe.source(Opportunity.getSObjectType())
            .field('Id') // ← Forgetting to select 'Name'
            .whereEqual('Id', this.oppId);
 
        IEntry oppEntry = this.eloquent.first(scribe);
 
        // This line will throw an exception because 'Name' was not selected.
        System.debug('Opportunity Name: ' + oppEntry.getName()); 
 
        Opportunity opp = (Opportunity) oppEntry.getRecord();
        opp.Name = 'Updated Name';
        return(Opportunity) this.eloquent.doUpdate(opp);
    }
}

The Test Class (Catches the Error)

APEX
@isTest
private class OppNameUpdaterTest {
    @isTest
    static void testExecute_WhenNameNotSelected_ThrowsException() {
        // Arrange
        IEloquent mockEloquent = new MockEloquent(
            MockEntry.of(Opportunity.getSObjectType()).autoId('1')
        );
        OppNameUpdater updater = new OppNameUpdater('006000000000001AAA', mockEloquent);
 
        // Act & Assert
        try {
            updater.execute();
            Assert.fail('Expected a QueryException to be thrown.');
        } catch(QueryException e) {
            // Assert that the helpful error message is correct
            String expectedMessage = 'The specified field is not selected in Scribe. object name: Opportunity, field name: Name';
            Assert.areEqual(expectedMessage, e.getMessage());
        }
    }
}

👏 Perfect! We detected the missing Name field during our unit test, well before deploying to production.

🔄 Detailed Field Access Validation

MockEntry provides comprehensive validation for all field access patterns:

1. Direct Field Access

APEX
@isTest
static void testDirectFieldAccess() {
    // Only 'Id' selected, not 'Name'
    Scribe scribe = Scribe.source(Account.getSObjectType()).field('Id');
    MockEntry mockEntry = MockEntry.of(Account.getSObjectType()).autoId(1);
    mockEntry = (MockEntry) mockEntry.setFieldStructure(scribe.buildFieldStructure());
 
    // ❌ This throws QueryException
    String name = (String) mockEntry.get('Name');
}

2. getId() and getName() Methods

APEX
@isTest
static void testGetIdMethod() {
    // Only 'Name' selected, not 'Id'
    Scribe scribe = Scribe.source(Account.getSObjectType()).field('Name');
    MockEntry mockEntry = MockEntry.of(Account.getSObjectType()).add('Name', 'Test Account');
    mockEntry = (MockEntry) mockEntry.setFieldStructure(scribe.buildFieldStructure());
 
    try {
        Id accountId = mockEntry.getId(); // ❌ Id not selected
        Assert.fail('Expected QueryException');
    } catch(QueryException e) {
        Assert.areEqual(
            'The specified field is not selected in Scribe. object name: Account, field name: Id',
            e.getMessage()
        );
    }
}

🔗 Use Case 2: Detecting Omissions in Relationships

This powerful validation works for parent and child relationships, too. Here, the code forgets to select the parent Account's Name:

The Service Class (with relationship bug)

APEX
public Opportunity execute() {
    Scribe scribe = Scribe.source(Opportunity.getSObjectType())
        .field('Id')
        .parentField(
            Scribe.asParent('AccountId').field('Id') // ← Forgetting to select Account's 'Name'
        )
        .whereEqual('Id', this.oppId);
 
    IEntry oppEntry = this.eloquent.first(scribe);
 
    IEntry accountEntry = oppEntry.getParent('AccountId');
 
    // This line will now throw an exception
    System.debug('Account Name: ' + accountEntry.getName());
 
    // ...
}

The Test Class (Catches the Relationship Error)

APEX
@isTest
static void testExecute_WhenAccountNameNotSelected_ThrowsException() {
    // Arrange
    IEloquent mockEloquent = new MockEloquent(
        MockEntry.of(Opportunity.getSObjectType())
            .autoId(1)
            .addParent('AccountId', MockEntry.of(Account.getSObjectType()).autoId(1))
    );
    OppNameUpdater updater = new OppNameUpdater('006000000000001AAA', mockEloquent);
 
    // Act & Assert
    try {
        updater.execute();
        Assert.fail('Expected a QueryException to be thrown.');
    } catch(QueryException e) {
        // Assert that the error message correctly identifies the missing field in the parent
        String expected = 'The specified field is not selected in Scribe. object name: Account, field name: Name';
        Assert.areEqual(expected, e.getMessage());
    }
}

3. Parent Relationship Access Validation

APEX
@isTest
static void testParentAccess() {
    // Missing AccountId in field selection
    Scribe scribe = Scribe.source(Opportunity.getSObjectType()).field('Id');
    MockEntry mockEntry = MockEntry.of(Opportunity.getSObjectType())
        .autoId(1)
        .setFieldStructure(scribe.buildFieldStructure());
 
    try {
        IEntry parent = mockEntry.getParent('AccountId'); // ❌ AccountId not selected
        Assert.fail('Expected QueryException');
    } catch(QueryException e) {
        Assert.areEqual(
            'The specified parentIdFieldName is not set in Scribe. object name: Opportunity, parent Id field name: AccountId',
            e.getMessage()
        );
    }
}

4. Child Relationship Access

APEX
@isTest
static void testChildAccess() {
    // Child fields not properly selected
    Scribe scribe = Scribe.source(Account.getSObjectType())
        .field('Id')
        .withChildren(
            Scribe.asChild(Contract.getSObjectType()).field('Id')   // Missing 'Name' field
        );
 
    MockEntry mockEntry = MockEntry.of(Account.getSObjectType())
        .autoId(1)
        .addChildren(
            'Contract',
            new List<MockEntry>{
                MockEntry.of(Contract.getSObjectType()).add('Name', 'Mock Contract 1'), // set Name field value
                MockEntry.of(Contract.getSObjectType()).add('Name', 'Mock Contract 2')
            }
        );
    mockEntry = (MockEntry) mockEntry.setFieldStructure(scribe.buildFieldStructure());
 
    List<IEntry> contracts = mockEntry.getChildren('Contract');
 
    try {
        String contractName = (String) contracts[0].get('Name'); // ❌ Name value is set but not selected in scribe!
        Assert.fail('Expected QueryException');
    } catch(QueryException e) {
        Assert.areEqual(
            'The specified field is not selected in Scribe. object name: Contract, field name: Name',
            e.getMessage()
        );
    }
}

🧮 Use Case 3: Aggregate Query Validation

MockEntry also validates field access for aggregate queries, ensuring that aliases are correctly used:

The Service Class (with aggregate bug)

APEX
public class OpportunityAnalytics {
    private IEloquent eloquent;
 
    public OpportunityAnalytics(IEloquent eloquent) {
        this.eloquent = eloquent;
    }
 
    public void processAverages() {
        Scribe scribe = Scribe.source(Opportunity.getSObjectType())
            .field('StageName')
            .average('Amount', 'avgAmount');
 
        List<IEntry> results = this.eloquent.get(scribe);
 
        for(IEntry result : results) {
            Decimal avgAmount = (Decimal) result.get('avgAmount'); // ✅ Correct alias
            Decimal totalAmount = (Decimal) result.get('totalAmount'); // ❌ Wrong alias
            System.debug('Average: ' + avgAmount + ', Total: ' + totalAmount);
        }
    }
}

The Test Class (Catches the Aggregate Error)

APEX
@isTest
static void testAnalytics_WhenAccessingWrongAlias_ThrowsException() {
    // Arrange: Set up the mock aggregate result
    IEloquent mockEloquent = new MockEloquent(
        new MockEntry(new Map<String, Object>{
            'StageName' => 'Prospecting', 
            'avgAmount' => 5000
        })
    );
    OpportunityAnalytics service = new OpportunityAnalytics(mockEloquent);
 
    // Act & Assert
    try {
        service.processAverages();
        Assert.fail('Expected a QueryException to be thrown.');
    } catch(QueryException e) {
        // Assert that the error message correctly identifies the missing alias
        String expected = 'The specified field or alias is not exist in Scribe. field or alias name: totalAmount';
        Assert.areEqual(expected, e.getMessage());
    }
}

Additional Aggregate Validation Example

APEX
@isTest
static void testAggregateValidation() {
    Scribe scribe = Scribe.source(Opportunity.getSObjectType())
        .field('StageName')
        .average('Amount', 'avgAmount');
 
    MockEntry mockEntry = MockEntry.of(Opportunity.getSObjectType());
    mockEntry = (MockEntry) mockEntry.setFieldStructure(scribe.buildFieldStructure());
    mockEntry = mockEntry.add('StageName', 'Closed Won')
        .add('avgAmount', 5000);
 
    try {
        // ❌ Accessing non-existent field in aggregate result
        Object maxAmount = mockEntry.get('maxAmount');
        Assert.fail('Expected QueryException');
    } catch(QueryException e) {
        Assert.areEqual(
            'The specified field or alias is not exist in Scribe. field or alias name: maxAmount',
            e.getMessage()
        );
    }
}

✅ This proves that the safety net works for both standard fields and aggregate aliases.

🔧 Integration with Business Logic

MockEntry validation works seamlessly with your business logic classes:

APEX
public class AccountService {
    private IEloquent eloquent;
 
    public AccountService(IEloquent eloquent) {
        this.eloquent = eloquent;
    }
 
    public List<Account> getAccountsWithIndustry() {
        // ❌ Query missing 'Industry' field
        Scribe scribe = Scribe.source(Account.getSObjectType())
            .fields(new List<String>{'Id', 'Name'})
            .whereEqual('Type', 'Customer');
 
        List<IEntry> entries = eloquent.get(scribe);
        List<Account> accounts = new List<Account>();
 
        for(IEntry entry : entries) {
            Account acc = new Account();
            acc.Id = entry.getId();
            acc.Name = (String) entry.get('Name');
            acc.Industry = (String) entry.get('Industry'); // ❌ Will fail here
            accounts.add(acc);
        }
 
        return accounts;
    }
}
 
@isTest
static void testAccountService() {
    // Arrange
    MockEloquent mockEloquent = new MockEloquent();
    AccountService service = new AccountService(mockEloquent);
 
    // Act & Assert - Test catches the missing field selection
    try {
        service.getAccountsWithIndustry();
        Assert.fail('Expected QueryException for missing Industry field');
    } catch(QueryException e) {
        // Test correctly identifies the production bug
        Assert.isTrue(e.getMessage().contains('Industry'));
    }
}

Two Safety Nets Beyond SELECT Omission

All four cases above came down to "check the Scribe's SELECT clause against the access". Two other mechanisms close the same "green while verifying nothing" hole.

Forgetting to feed the mock throws

In a test that uses labels, forgetting attach('X', ...) while fetching under label('X') — or mistyping the label — makes that query return zero rows. You fall into the "nothing to do, skip" branch and the test goes green having verified nothing.

Today, calling get / first / firstOrFail under a label that was never attached throws, and the error lists the labels that were attached.

When you genuinely want the zero-row path, attach an empty list to declare it.

APEX
MockEloquent mock = (new MockEloquent())
  .attach(MyUsecase.LBL_FETCH, new List<IEntry>());

The point of the design is that "not attached" and "attached empty" are distinct states.

Entries handed over directly can carry a contract too

The four cases above concerned entries returned through MockEloquent; they pass through a Scribe, so the contract attaches automatically.

Entries handed straight to the SUT have no contract. The classic case is a batch: records reaching execute(bc, scope) never pass through IEloquent, so production is covered by the platform (they are real query results) while tests check nothing.

fetchedBy(scribe) attaches the contract after the fact.

APEX
MockEntry card = MockEntry.of(BusinessCard__c.class)
  .autoId(1)
  .set('CompanyName__c', 'Acme')
  .fetchedBy(RematchCompanyCardsHandler.scope());

Pull the query construction into a @TestVisible method and your test hands over exactly the same Scribe production uses. See API Reference: Scribe.

📝 Summary

FeatureDescription
Automatic ValidationMockEntry throws an exception if you access a field not selected in Scribe.
High-Fidelity MocksThe mock system reproduces the exact validation behavior of a live query.
Full Relationship SupportThis validation works across parent, child, and many-to-many relationships.
Aggregate Query SupportValidates access to aggregate function aliases and prevents wrong field access.
Prevents Production ErrorsGives you confidence that a test that passes will also work in production.
💡 Info

Database-less tests that don't miss query defects.

This is the core of ApexEloquent's testing philosophy. It provides a robust safety net that elevates your testing strategy to the next level.

MockEntry's false positive detection ensures your tests reflect real production behavior, giving you true confidence in your code quality and preventing unexpected runtime failures. By catching field selection errors during development rather than in production, you can deploy with confidence knowing your SOQL queries are complete and correct.

🔗 Back to Guide

Back to ApexEloquent Developer Guide