3

I have a schedulable class that runs a report and send an email with the results of that report. Everything works fine when I'm testing it with the report filled with data, but when the report is empty and I want to create this mock data, the reports always appears empty.

@isTest(SeeAllData='true')
public static void testReportsWithData() {
    Contact c = new Contact(checkbox__c = FALSE);
    insert c;
    c.checkbox__c = TRUE;
    update c;

    String sch = '0 0 0 * * ?';
    Test.startTest();
    System.schedule('SchedReport', sch, new SchedReport());
    Test.stopTest();
}

The report conditions are: checkbox = TRUE AND edit date = TODAY. And this is the execute of the SchedReport() class.

global void execute(SchedulableContext sc) {
    Report report = [SELECT Id FROM Report WHERE DeveloperName = 'reportAPIName' LIMIT 1];
    Id reportId = report.Id;

    Reports.ReportMetadata reportMetadata = Reports.ReportManager.describeReport(reportId).getReportMetadata();
    Reports.ReportResults result = Reports.ReportManager.runReport(reportId, reportMetadata, true);
    Integer numOfRecords = Integer.valueOf(result.getFactMap().get('T!T').getAggregates().get(0).getValue());

    if (numOfRecords > 0) {
        // program functionality
    }
}

It always gets 0 in the numOfRecords variable if I have not filled the report using the CRM (modifying a contact using the details page or a SOQL query). Besides, the checkbox should change based on a workflow rule that set the checkbox to TRUE anytime the email of the contact change, but I cannot make that work in test environment for some reason so I force it by changing the checkbox in the update.

Sorry for the extension of the question, and thanks for the help.

  • 1
    Two things: (1) does the behavior change if you directly call execute() rather than scheduling the class? (2) if you write a query for the data that your report should return and assert that the data is there prior to running the report, does the assertion pass? – David Reed Feb 4 at 13:54
  • Ok, not even try the second thing, solved. When I use the sch.execute(null) it covers a 95% of the code so it works properly. Thank you SO much. Again. – mbayon Feb 4 at 14:03
  • 1
    I'll post an answer and I'm going to experiment a little more to see if I can identify the root cause. I'm not sure if it's other async code in your org interfering or if it's special behavior from the Report API in async context. – David Reed Feb 4 at 14:10
2

Despite that you are using a synchronous report run, the issue appears to stem from the report run taking place within an asynchronous context that's fired by Test.stopTest().

It's documented in Test Reports that

In Apex tests, asynchronous reports execute only after the test is stopped using the Test.stopTest method.

Emphasis mine. If you were running an asynchronous report, the problem would be clear - it's async code inside async code, which doesn't run at Test.stopTest(). But that's not the case here.

The example Apex tests in Salesforce's documentation all depict running a synchronous report in synchronous code, without the use of Test.startTest() and Test.stopTest().

It's not clear based on what we've seen here that sync-report-within-async-code specifically is the problem, or if there might be some other asynchronous code running in your Salesforce instance that is fired when Test.stopTest() executes and which destroys your test data's applicability to the report. (This is a real possibility!)

Regardless, it sounds like, based on your comment, that you were able to complete the testing successfully by calling execute() synchronously.

Reproduction / Root Cause

Based on a reproduction I did in a Trailhead playground, I believe the issue is related to some other asynchronous code or automation in your org that changes your data to make it not appear on the report - not to inherent limitations of the Reports and Dashboards Apex API.

I built a new Contacts and Accounts report with no filters in an org that contains no Contacts. I added the following code:

Q249004.apxc

public class Q249004 implements Schedulable {
    public void execute(SchedulableContext sc) {
        Reports.ReportMetadata reportMetadata = Reports.ReportManager.describeReport('00O1K0000094fuPUAQ').getReportMetadata();
        Reports.ReportResults result = Reports.ReportManager.runReport('00O1K0000094fuPUAQ', reportMetadata, true);
        Integer numOfRecords = Integer.valueOf(result.getFactMap().get('T!T').getAggregates().get(0).getValue());

        System.assert(numOfRecords > 0);
    }
}

TestQ249004.apxc

@isTest
public class TestQ249004 {
    @isTest
    public static void runTest() {
        Account a = new Account(Name = 'Test');
        insert a;

        Contact c = new Contact(LastName = 'Test2', AccountId = a.Id);
        insert c;

        String sch = '0 0 0 * * ?';
        Test.startTest();
        System.schedule('Q249004', sch, new Q249004());
        Test.stopTest();
    }
}

Results

The test passes, indicating that it's fine to invoke a synchronous report run via Apex in an asynchronous context. If insert c is commented out, the test fails (as expected), which indicates that it really is the presence or absence of data created within the test context that governs the success or failure of the test case.

Look to other automation within your org for the true cause of your issue, and consider writing assertions in your Schedulable class against the data you expect to be there in order to identify the problem.

seeAllData=true

As a side note, because I didn't know this and it's quite interesting, the Reports and Dashboards Apex support inherently runs with seeAllData=true, whether or not it's specified:

In Apex tests, report runs always ignore the SeeAllData annotation, regardless of whether the annotation is set to true or false. This means that report results will include pre-existing data that the test didn’t create. There is no way to disable the SeeAllData annotation for a report execution. To limit results, use a filter on the report.

  • last para in yellow - I did not know this either and for a PROD deployment, you really need to use filters as otherwise the report execution time could be too long – cropredy Feb 5 at 0:36

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.