[SalesForce] Queueable apex still executes in test context even if Enqueue Job is not called between Test.startTest() and Test.stopTest()

According to Salesforce docs:

All asynchronous calls made after the startTest method are collected
by the system. When stopTest is executed, all asynchronous processes
are run synchronously.

Now I was assuming this holds true for all async apex, but for the queueable apex, it executes even if we don't enqueue a job between start test and stop test.

Minimum Viable Code to reproduce:

Queueable Apex :

public class MyQueueableApex implements Queueable{
    public void execute(QueueableContext context) {
        System.debug('Inside Queueable apex');
    }
}

Test class:

@isTest
public class MyQueueableApexTest {

    @isTest
    public static void queuableTest(){

        Id jobId = System.enqueueJob(new MyQueueableApex());
        System.assertNotEquals('Completed' , [SELECT Id , Status FROM AsyncApexJob WHERE Id=:jobId][0].Status);
    }
}

Debug:

48.0 APEX_CODE,DEBUG;APEX_PROFILING,INFO;CALLOUT,INFO;DB,INFO;NBA,INFO;SYSTEM,DEBUG;VALIDATION,INFO;VISUALFORCE,INFO;WAVE,INFO;WORKFLOW,INFO
16:24:33.0 (404217)|USER_INFO|[EXTERNAL]|00528000000PIGY|pjxdsfse@gmail.com|(GMT+00:00) Greenwich Mean Time (Europe/London)|GMTZ
16:24:33.0 (445053)|EXECUTION_STARTED
16:24:33.0 (450671)|CODE_UNIT_STARTED|[EXTERNAL]|01p0I00000GAtQS|MyQueueableApexTest.queuableTest()
16:24:33.0 (11522220)|SOQL_EXECUTE_BEGIN|[8]|Aggregations:0|SELECT Id, Status FROM AsyncApexJob WHERE Id = :tmpVar1
16:24:33.0 (15845130)|SOQL_EXECUTE_END|[8]|Rows:1
16:24:33.16 (16115510)|CUMULATIVE_LIMIT_USAGE
16:24:33.16 (16115510)|LIMIT_USAGE_FOR_NS|(default)|
  Number of SOQL queries: 1 out of 100
  Number of query rows: 1 out of 50000
  Number of SOSL queries: 0 out of 20
  Number of DML statements: 0 out of 150
  Number of DML rows: 0 out of 10000
  Maximum CPU time: 0 out of 10000
  Maximum heap size: 0 out of 6000000
  Number of callouts: 0 out of 100
  Number of Email Invocations: 0 out of 10
  Number of future calls: 0 out of 50
  Number of queueable jobs added to the queue: 1 out of 50
  Number of Mobile Apex push calls: 0 out of 10

16:24:33.16 (16115510)|CUMULATIVE_LIMIT_USAGE_END

16:24:33.0 (16157837)|CODE_UNIT_FINISHED|MyQueueableApexTest.queuableTest()
16:24:33.0 (17551568)|EXECUTION_FINISHED
16:24:33.27 (27032903)|USER_INFO|[EXTERNAL]|00528000000PIGY|pjxdsfse@gmail.com|(GMT+00:00) Greenwich Mean Time (Europe/London)|GMTZ
16:24:33.27 (27047994)|EXECUTION_STARTED
16:24:33.27 (27051597)|CODE_UNIT_STARTED|[EXTERNAL]|01p0I00000GAtQN|MyQueueableApex
16:24:33.27 (28544098)|USER_DEBUG|[3]|DEBUG|Inside Queueable apex
16:24:33.27 (28634595)|CODE_UNIT_FINISHED|MyQueueableApex
16:24:33.27 (29986879)|EXECUTION_FINISHED

The status of async-apex job is still Pending, I can see from debug log. execute method was called even if I didn't use start , stop test at all. Is it expected behaviour documented somewhere?

The issue I faced is, I was unit-testing if queueable is being queued with dummy value, I didn't care about if it was successful or not, but my unit test broke cause Apex Test execute queueabale, and due to dummy values, SOQL failed.

Also this document states:

The ID of a queueable Apex job isn’t returned in test
context—System.enqueueJob returns null in a running test.

I could still see enqueueJob returning proper job id and not null.

Best Answer

I wrote a unit test that describes how all asynchronous code runs at the end of a unit test. This was in 2018, and still not fixed (and likely won't be).

The documentation is also incorrect, as far as I can tell (System.enqueueJob returning an ID in test context). Report a bug on Twitter to @salesforcedocs. They'll put something in for you.

If you do not call Test.stopTest, but you still want to kill any potential asynchronous jobs, use Database.rollback.

Test.startTest();
Savepoint sp = Database.setSavePoint();
// Do stuff here //
Database.rollback(sp);

Doing this will roll back all parts of the transaction, including calling future methods, queueable methods, scheduled methods, batchable methods, emails, post-commit platform events, etc.

Related Topic