Apex Integration Tests for Agentforce and Data 360 Services (Developer Preview)

Use Apex integration tests to write end-to-end tests that exercise real interactions between your Salesforce org and services such as Agentforce and Data 360. Unlike standard Apex unit tests, integration tests relax callout restrictions and transaction rollback semantics, so your tests can make real service calls, commit data mid-transaction, and make assertions on expected outcomes. As a developer preview feature, integration tests are available only in scratch orgs. You can’t run them in production orgs or during metadata deployments.

The Apex Integration Tests feature is available as a developer preview in scratch orgs in Summer ’26 (API version 67.0). The feature isn’t generally available unless or until Salesforce announces its general availability in documentation or in press releases or public statements. All commands, parameters, and other features are subject to change or deprecation at any time, with or without notice. Don't implement functionality developed with these commands or tools in your production package.

Note

How Integration Tests Compare to Unit Tests

Integration tests complement, rather than replace, existing Apex unit tests. Unit tests remain the right choice for testing isolated business logic, triggers, and flows. Integration tests are for scenarios that require real service interactions across transaction boundaries. Use integration tests used in these ways.

  • Test Agentforce callouts and verify their side effects on org data.
  • Run real SOQL queries against Data 360 data model objects (DMOs) without stub mocks.
  • Test behavior that depends on committed data, such as field history tracking across updates.

This table shows the key differences between standard Apex unit tests(@IsTest) and integration tests (@IntegrationTest).

Unit Tests Integration Tests
Agentforce and Data 360 callouts Blocked; require mocks Allowed
Transaction behavior Auto-rollback Data committed. Use @TearDown for cleanup
Data visibility Test data silo by default SeeAllData=true by default
Code coverage Counts toward deployment code coverage requirements Doesn’t count toward deployment code coverage requirements
Metadata deployments Included. Unit tests are xecuted in metadata deployment. Excluded
Execution Synchronous or asynchronous Asynchronous only. Allowed only 1 concurrent execution per org.
Maximum runtime Standard limits 10 minutes

Enable Integration Tests in a Scratch Org Definition File

Before you can run Apex integration tests, you must have a scratch org with the ApexIntegrationTests feature enabled.

To enable the feature, add ApexIntegrationTests to the features array in the config/project-scratch-def.json of your Salesforce DX project.

1{
2  "orgName": "Company",
3  "edition": "Developer",
4  "features": ["ApexIntegrationTests"]
5}

To create a scratch org with this definition, use the org create scratch Salesforce CLI command.

For more information about scratch org development, see Scratch Orgs in the Salesforce DX Developer Guide.

Create an Integration Test Class

To create an Apex integration test class, use these annotations and methods.

  • @IntegrationTest: This annotation marks a class and its test methods as integration tests. A class annotated with @IntegrationTest can only contain integration test methods and @TearDown methods. You can’t annotate a class with both @IntegrationTest and @IsTest annotations. You can’t call integration test methods from non-test contexts or from @IsTest test methods. However, integration tests can call methods in @IsTest utility classes such as shared test data factories.
  • @TearDown: This annotation marks a static cleanup method that runs after the test completes, regardless of whether the test passes or fails. Use this method to clean up committed test data. The teardown transaction auto-commits at the end of the execution.
  • IntegrationTest.commitTestOnly(): This method commits data to the database mid-transaction so that it’s visible to other threads and services. It resets the uncommitted work checkpoint, so subsequent callouts don’t fail. The method also resets tracking for mixed DML operations, so you can perform setup sObject and non-setup sObject DML in separate commit windows. The method can only be called from an @IntegrationTest method. Calling it from outside an integration test context results in a runtime error: Test.commitTestOnly() can only be called from integration testMethods.

This basic integration test verifies that the service can insert an account.

1@IntegrationTest
2public class MyServiceIntegrationTest {
3
4    @IntegrationTest
5    public static void testServiceInteraction() {
6        Account a = new Account(Name = 'Integration Test Account');
7        insert as user a;
8        IntegrationTest.commitTestOnly();
9
10        Account result = [SELECT Id, Name FROM Account WHERE Id = :a.Id WITH USER_MODE];
11        Assert.areEqual('Integration Test Account', result.Name);
12    }
13
14    @TearDown
15    public static void tearDown() {
16        delete as user [SELECT Id FROM Account WHERE Name = 'Integration Test Account'];
17    }
18}

Test an Agent Action

An integration test can invoke an agent with an invocable action and assert on the response. The commitTestOnly() call before the agent invocation is critical. Without it, the record you created exists only in your pending transaction and is invisible to the Agentforce planner service.

1@IntegrationTest
2public with sharing class AgentforceIntegrationTest {
3
4    @IntegrationTest
5    public static void testAgentSummarizesAccount() {
6
7        Account a = new Account(Name = 'AgentDemoAccount', AnnualRevenue = 1000000);
8        insert as user a;
9        IntegrationTest.commitTestOnly();
10
11        Invocable.Action action = Invocable.Action.createCustomAction(
12            'generateAiAgentResponse',
13            'Demo_Action'
14        );
15        action.setInvocationParameter('userMessage', 'Summarize my Account ' + a.Id);
16
17        List<Invocable.Action.Result> results = action.invoke();
18        String response = results[0].getOutputParameters().get('agentResponse').toString();
19
20        Assert.isNotNull(response, 'Agent should return a response');
21        Assert.isTrue(response.contains('AgentDemoAccount'),
22            'Response should reference the account name');
23    }
24
25    @TearDown
26    public static void tearDown() {
27        delete as user [SELECT Id FROM Account WHERE Name = 'AgentDemoAccount'];
28    }
29}

Generate a report using the AdvancedInputBindings agent script recipe

This example uses the AdvancedInputBindings agent script recipe and generates a sales report for Q2 2026.

1@IntegrationTest
2public with sharing class AdvancedInputBindingsIntegrationTest {
3    @IntegrationTest
4    public static void testAgentUpdate() {
5        // Invoke Agentforce service (callout allowed in integration tests)
6        Invocable.Action action = Invocable.Action.createCustomAction(
7            'generateAiAgentResponse',
8            'AdvancedInputBindings'
9        );
10    
11        String prompt = 'Generate a sales report for Q2 2026';
12        action.setInvocationParameter('userMessage', prompt);
13        List<Invocable.Action.Result> results = action.invoke();
14
15        String response = results[0].getOutputParameters().get('agentResponse').toString();
16        Assert.isTrue(response.contains('a0'), 'Expected agent response to create an sobject');
17    
18        // query and assert the report was created
19        List<ASR_Report_Log__c> logs = [
20            SELECT Id, Report_Type__c, User_ID__c, Format__c
21            FROM ASR_Report_Log__c
22            WHERE Start_Date__c = 2026-04-01
23        ];
24        Assert.areEqual(1, logs.size(), 'Report log should be created');
25    }
26
27    @tearDown
28    public static void destroyTestRecords() {
29        List<ASR_Report_Log__c> testRecords = [
30            SELECT Id, Report_Type__c, User_ID__c, Format__c
31            FROM ASR_Report_Log__c
32            WHERE Start_Date__c = 2026-04-01
33        ];
34    
35        if (testRecords.size() > 0) {
36            delete testRecords;
37        }
38    }
39}

Test a Data 360 SOQL Query

Query Data 360 data model objects (DMOs) directly in integration tests without using SoqlStubProvider or Test.createSoqlStub. In standard unit tests, SOQL queries against DMO objects require stub mocks. Integration tests bypass this restriction for Data 360 entities.

1@IntegrationTest
2public with sharing class DataCloudQueryIntegrationTest {
3
4    @IntegrationTest
5    public static void testDMOQuery() {
6        List<SObject> rows = Database.query('SELECT Id FROM Account__dlm WITH USER_MODE LIMIT 1');
7        Assert.areEqual(1, rows.size(), 'Data 360 query should return 1 row');
8    }
9}

Tests as a Specific User

Use System.runAs() to run integration test logic as a specific user, including setting up the necessary permission set assignments.

1@IntegrationTest
2public with sharing class RunAsIntegrationTest {
3
4    @IntegrationTest
5    public static void testAgentResponseAsStandardUser() {
6        User u = [SELECT Id FROM User WHERE Alias = 'tstUsr' LIMIT 1];
7
8        System.runAs(u) {
9            Account a = new Account(Name = 'AgentDemoAccount');
10            insert as user a;
11            IntegrationTest.commitTestOnly();
12
13            Invocable.Action action = Invocable.Action.createCustomAction(
14                'generateAiAgentResponse',
15                'Demo_Action'
16            );
17            String prompt = 'Summarize my Account ' + a.Id;
18            action.setInvocationParameter('userMessage', prompt);
19            List<Invocable.Action.Result> results = action.invoke();
20
21            String response = results[0].getOutputParameters()
22                .get('agentResponse').toString();
23            Assert.isTrue(response.contains('AgentDemoAccount'));
24        }
25    }
26
27    @TearDown
28    public static void tearDown() {
29        delete as user [SELECT Id FROM Account WHERE Name = 'AgentDemoAccount'];
30    }
31}

Test External HTTP Callouts

Regular HTTP callouts to external endpoints other than Agentforce and Data 360 still require mocks in integration tests.

1@IntegrationTest
2public with sharing class HttpCalloutMockIntegrationTest implements HttpCalloutMock {
3
4    public HTTPResponse respond(HTTPRequest req) {
5        HttpResponse res = new HttpResponse();
6        res.setHeader('Content-Type', 'application/json');
7        res.setBody('{"status":"success"}');
8        res.setStatusCode(200);
9        return res;
10    }
11
12    @IntegrationTest
13    public static void testExternalApiWithMock() {
14        Test.setMock(HttpCalloutMock.class, new HttpCalloutMockIntegrationTest());
15
16        Account a = new Account(Name = 'Callout Test Account');
17        insert as user a;
18        IntegrationTest.commitTestOnly();
19
20        Http h = new Http();
21        HttpRequest req = new HttpRequest();
22        req.setEndpoint('https://external-api.example.com/api');
23        req.setMethod('POST');
24        req.setBody('{"accountId":"' + a.Id + '"}');
25
26        HttpResponse res = h.send(req);
27
28        Assert.areEqual(200, res.getStatusCode());
29        Assert.isTrue(res.getBody().contains('success'));
30    }
31
32    @TearDown
33    public static void tearDown() {
34        delete as user [SELECT Id FROM Account WHERE Name = 'Callout Test Account'];
35    }
36}

Use Test.startTest() and Test.stopTest() with Asynchronous Apex

Asynchronous Apex operations enqueued in integration tests can be dequeued synchronously using the Test.startTest() and Test.stopTest() pattern.

1@IntegrationTest
2public with sharing class AsyncIntegrationTest {
3
4    public with sharing class AccountProcessorQueueable implements Queueable {
5        private Id accountId;
6
7        public AccountProcessorQueueable(Id accountId) {
8            this.accountId = accountId;
9        }
10
11        public void execute(QueueableContext context) {
12            Account a = [SELECT Id, Name FROM Account WHERE Id = :accountId WITH USER_MODE];
13            a.Industry = 'Technology';
14            update as user a;
15        }
16    }
17
18    @IntegrationTest
19    public static void testQueueableExecution() {
20        Account a = new Account(Name = 'Async Test Account');
21        insert as user a;
22        IntegrationTest.commitTestOnly();
23
24        Test.startTest();
25        Id jobId = System.enqueueJob(new AccountProcessorQueueable(a.Id));
26        Test.stopTest();
27
28        Account updated = [SELECT Industry FROM Account WHERE Id = :a.Id WITH USER_MODE];
29        Assert.areEqual('Technology', updated.Industry,
30            'Queueable should have updated the industry');
31
32        AsyncApexJob job = [
33            SELECT Status, NumberOfErrors
34            FROM AsyncApexJob WHERE Id = :jobId
35            WITH USER_MODE
36        ];
37        Assert.areEqual('Completed', job.Status);
38        Assert.areEqual(0, job.NumberOfErrors);
39    }   
40    @TearDown
41    public static void tearDown() {
42        delete as user [SELECT Id FROM Account WHERE Name = 'Async Test Account'];
43    }
44}

Create Multiple Commits Within a Transaction

Use IntegrationTest.commitTestOnly() multiple times with a test method to commit data progressively, which is useful when later operations depend on previously committed records.

1@IntegrationTest
2public with sharing class MultiCommitIntegrationTest {
3
4    @IntegrationTest
5    public static void testMultipleCommits() {
6        Account a = new Account(Name = 'Commit Test Account');
7        insert as user a;
8        IntegrationTest.commitTestOnly();
9
10        a.Website = 'example.com';
11        update as user a;
12        IntegrationTest.commitTestOnly();
13
14        Contact c = new Contact(
15            FirstName = 'Test',
16            LastName = 'Contact',
17            AccountId = a.Id
18        );
19        insert as user c;
20        IntegrationTest.commitTestOnly();
21
22        Assert.areEqual(1,
23            [SELECT COUNT() FROM Account WHERE Name = 'Commit Test Account' WITH USER_MODE]);
24        Assert.areEqual(1,
25            [SELECT COUNT() FROM Contact WHERE AccountId = :a.Id WITH USER_MODE]);
26    }
27
28    @TearDown
29    public static void tearDown() {
30        delete as user [SELECT Id FROM Contact WHERE LastName = 'Contact'];
31        delete as user [SELECT Id FROM Account WHERE Name = 'Commit Test Account'];
32    }
33}

Test Field History Tracking

Standard unit tests roll back all data, so field history records are never created. Integration tests commit data, which makes it possible to assert on field history.

1@IntegrationTest
2public with sharing class FieldHistoryIntegrationTest {
3
4    @IntegrationTest
5    public static void testFieldHistoryIsTracked() {
6        Account a = new Account(Name = 'HistoryTestAccount', Website = 'example.com');
7        insert as user a;
8        IntegrationTest.commitTestOnly();
9
10        a.Website = 'salesforce.com';
11        update as user a;
12        IntegrationTest.commitTestOnly();
13
14        List<AccountHistory> history = [
15            SELECT Id FROM AccountHistory WHERE AccountId = :a.Id
16            WITH USER_MODE
17        ];
18        Assert.isTrue(history.size() > 0, 'Expected field history to be tracked');
19    }
20
21    @TearDown
22    public static void tearDown() {
23        delete as user [SELECT Id FROM Account WHERE Name = 'HistoryTestAccount'];
24    }
25}

Run an Integration Test

Both the test method and the teardown method commit their transactions automatically at the end of execution. Data created during the test persists unless explicitly deleted in the teardown.

Integration tests are considered a distinct “IntegrationTest” category that’s separate from Apex unit tests, flow tests, and Agentforce tests. You can discover and run them using the Salesforce CLI or the Tooling API, either a single test class synchronously or multiple test classes asynchronously. Integration tests are excluded from RunAllTests during metadata deployments.

Integration Testing Best Practices

  • Always implement @TearDown methods. Because integration tests commit data to the database, test data that is not torn down after the test run can impact subsequent runs. Delete records in the correct order in @TearDown. When you create related records, delete child records before parent records to avoid foreign key constraint issues.
  • Use commitTestOnly() before callouts to Agentforce and Data 360. These services operate on separate threads that can’t see uncommitted data in your test’s pending transaction.
  • Keep integration tests focused on integration concerns. Test isolated business logic with standard @IsTest unit tests. Reserve integration tests for scenarios that require real service interactions.
  • Be aware of concurrency limits. Only one integration test can run per org at a time. Design your test suite accordingly, and prefer shorter, more focused tests over long monolithic ones. Integration tests have access to and can commit data to your org (seeAllData=true). Be mindful of other requests that may be trying to write to the same data rows in your org if you use existing data from your org.
  • Create setup data and avoid reusing org data to avoid row lock contention.
  • Plan for the 10-minute runtime limit. If a test involves multiple slow service calls, consider splitting it into separate test methods.
  • Mock external HTTP endpoints. Even in integration tests, regular HTTP callouts to non-Salesforce endpoints require HttpCalloutMock. Only Agentforce and Data 360 services are exempt from the mock requirement.

Integration Testing Limitations and Considerations

  • During developer preview, integration testing can only run in scratch orgs. Integration tests aren’t available in production orgs, sandboxes, or during metadata deployments.
  • Integration tests don’t count toward code coverage requirements.
  • There is no @TestVisible access. Unlike @IsTest classes, @IntegrationTest classes can’t access private members annotated with @TestVisible in the test classes.
  • Only asynchronous execution is supported. Only one test runs per org at a time.
  • Asynchronous Apex governor limits (for SOQL, DML, CPU and heap limits) apply to integration testing. See Execution Governors and Limits.
  • Integration tests share the same 24-hour limit on asynchronous Apex test runs with flow tests and unit tests.