[SalesForce] way I can mock an AggregateResult

I'm trying to write most of my tests without hitting salesforce database. For that I'm implementing some simple mock class where I just return some SOQL queries when needed. The problem I'm facing now is with AggregateResult. Was someone able to create in memory this so that I can returned it in one of my mocked methods?

The query I want to mock in this particular case is:
[SELECT MIN(CreatedDate) minCaseCreateDate FROM Case WHERE Id in :caseIds]

Best Answer

Check edit history for my initial approach. After revisiting this question and with a bit more experience, I would pursue this path:

public class Aggregate
{
    public static List<Aggregate> query(List<AggregateResult> results)
    {
        return service.convert(results);
    }

    final Map<String, Object> data;
    public Aggregate(AggregateResult result)
    {
        data = result.getPopulatedFieldsAsMap();
    }
    public Object get(String alias) { return data.get(alias); }

    @TestVisible Aggregate() { data = new Map<String, Object>(); }
    @TestVisible Aggregate put(String alias, Object value)
    {
        data.put(alias, value);
        return this;
    }

    static Service service = new Service();
    @TestVisible static void setMock(Service mock) { service = mock; }
    @TestVisible virtual class Service
    {
        protected virtual List<Aggregate> convert(List<AggregateResult> results)
        {
            List<Aggregate> aggregates = new List<Aggregate>();
            for (AggregateResult result : results)
                aggregates.add(new Aggregate(result));
            return aggregates;
        }
    }
}

I was able to reference this query quite similar to how it would normally look:

List<Aggregate> aggregates = Aggregate.query([
    SELECT ProfileId, count(Id) records
    FROM User GROUP BY ProfileId
]);
for (Aggregate aggregate : aggregates)
{
    system.debug(aggregate.get('ProfileId') + ' - ' + aggregate.get('records'));
}

And it would be quite easy to mock:

@IsTest
class AggregateTests
{
    class Mock extends Aggregate.Service
    {
        final List<Aggregate> aggregates = new List<Aggregate>();
        public Mock add(Aggregate aggregate)
        {
            aggregates.add(aggregate);
            return this;
        }
        public override List<Aggregate> convert(List<AggregateResult> results)
        {
            return aggregates;
        }
    }
    @IsTest static void testMocking()
    {
        Id dummyId = SObjectType.Profile.getKeyPrefix().rightPad(15, '0');

        Mock fakeData = new Mock().add(new Aggregate()
            .put('ProfileId', dummyId)
            .put('records', 42));

        Test.startTest();
            Aggregate.setMock(fakeData);
            List<Aggregate> aggregates = Aggregate.query([
                SELECT ProfileId, count(Id) records
                FROM User GROUP BY ProfileId
            ]);
        Test.stopTest();

        system.assertEquals(1, aggregates.size(), 'Only mock data should be returned');
        system.assertEquals(dummyId, aggregates[0].get('ProfileId'), 'Only mock data should be returned');
        system.assertEquals(42, aggregates[0].get('records'), 'Only mock data should be returned');
    }
}
Related Topic