Skip to main content

Apex Test Class Examples for @HttpPost Exposed WebService Class

๐Ÿ’ฌ In plain words: Unit testing an Apex REST service marked with @HttpPost requires manually setting up RestContext.request and RestContext.response, populating URI parameters or request payloads, and executing positive and negative assertion tests against database state.

In Salesforce, the Apex programming language allows you to create powerful web services exposed to external systems for seamless data integration. A common scenario involves using the @HttpPost annotation within a @RestResource class to accept incoming REST requests. In this post, we'll walk through writing effective test classes for an @HttpPost Apex REST web service, setting up RestContext, and handling positive and negative scenarios to achieve robust code coverage.

Example Scenario

Consider a scenario where we track expenses using a custom object named Expense__c. We need an Apex REST endpoint exposed at /services/apexrest/expenseService that accepts incoming JSON payloads to insert new expense records.

1. The Web Service Class

Below is the implementation of our ExpenseWebService class containing the @HttpPost endpoint:

@RestResource(urlMapping='/expenseService')
global with sharing class ExpenseWebService {
    
    @HttpPost
    global static String createExpense(String expenseName, Decimal amount) {
        if (String.isBlank(expenseName) || amount == null || amount < 0) {
            return 'Error creating expense: Invalid input data';
        }
        
        Expense__c newExpense = new Expense__c(Name = expenseName, Amount__c = amount);
        insert newExpense;
        return 'Expense record created with ID: ' + newExpense.Id;
    }
}

2. The Unit Test Class

To unit test an Apex REST service, we instantiate RestRequest and RestResponse objects, attach them to RestContext, and then invoke the static method directly.

๐Ÿ’ก Key Steps for Testing Apex REST Services
  • Initialize RestRequest and set requestURI and httpMethod.
  • Pass parameters via request body (Blob.valueOf(...)) or direct method call parameters.
  • Assign request/response instances to RestContext.request and RestContext.response.
  • Perform assertions on database state (System.assertEquals) and returned response strings.
@isTest
private class TestExpenseWebService {

    @isTest 
    static void testCreateExpenseSuccess() {
        // Setup RestContext
        RestRequest req = new RestRequest();
        RestResponse res = new RestResponse();
        
        req.requestURI = '/services/apexrest/expenseService';
        req.httpMethod = 'POST';
        req.requestBody = Blob.valueOf('{"expenseName": "Sample Expense", "amount": 100.00}');
        
        RestContext.request = req;
        RestContext.response = res;

        // Execute service method
        Test.startTest();
        String result = ExpenseWebService.createExpense('Sample Expense', 100.00);
        Test.stopTest();

        // Verify result string
        System.assert(result.startsWith('Expense record created'), 'Response should indicate successful creation.');

        // Verify record insertion in database
        List<Expense__c> expenses = [SELECT Id, Name, Amount__c FROM Expense__c WHERE Name = 'Sample Expense'];
        System.assertEquals(1, expenses.size(), 'One expense record should be created.');
        System.assertEquals(100.00, expenses[0].Amount__c, 'Amount should match the input parameter.');
    }

    @isTest 
    static void testCreateExpenseInvalidData() {
        // Setup RestContext
        RestRequest req = new RestRequest();
        RestResponse res = new RestResponse();
        
        req.requestURI = '/services/apexrest/expenseService';
        req.httpMethod = 'POST';
        req.requestBody = Blob.valueOf('{"expenseName": "", "amount": -50.00}');
        
        RestContext.request = req;
        RestContext.response = res;

        // Execute service method
        Test.startTest();
        String result = ExpenseWebService.createExpense('', -50.00);
        Test.stopTest();

        // Assert failure response
        System.assert(result.startsWith('Error creating expense'), 'Service should reject invalid inputs.');
        
        // Assert no records were inserted
        List<Expense__c> expenses = [SELECT Id FROM Expense__c];
        System.assertEquals(0, expenses.size(), 'No expense records should be created on validation failure.');
    }
}
⚠ RESTCONTEXT TESTING TRAP: Do not rely on real HTTP callouts inside unit tests! Apex unit tests run in isolation and cannot make actual HTTP calls. If you omit populating RestContext.request or try to invoke the REST endpoint via Http.send() without a mock, your test will throw a System.NullPointerException or System.CalloutException.
๐Ÿง  Key Takeaway: Testing exposed Apex REST endpoints requires assigning mock `RestRequest` and `RestResponse` instances directly to `RestContext` prior to invoking the endpoint method.
๐Ÿงญ 360 Card — Testing @HttpPost Apex REST Endpoints
  • Rule: Set up RestContext.request and RestContext.response before executing custom @RestResource methods in test classes.
  • Gain: Simulates real external inbound API requests isolated from actual web network dependencies.
  • Price: Requires manually building mock JSON request payloads (Blob.valueOf(...)) and request URIs.
  • Limits: Tests verify method execution and DML operations but do not validate external OAuth authentication or profile permission security policies.

Conclusion

Writing comprehensive test classes for @HttpPost REST endpoints in Salesforce requires setting up the execution context with RestContext. By covering success flows alongside validation failures, you ensure your endpoints remain reliable, resilient, and ready for production deployment.