Apex Testing — Part 2

Mukundan
5 min readDec 21, 2022

--

…Write better tests

This is Part 2 of the series on Apex Testing. Part 1 went over some basic guidelines and best practices for writing better Apex tests. We ended with introducing the “Arrange, Act, Assert” pattern to use for writing better tests.

This part will take a quick look at what Salesforce recommends you test and the different types of tests involved. We will also see some code examples of the “Arrange, Act, Assert” pattern. Please refer to the links under “Useful references” for documentation and additional examples.

What to test?

Whenever you create test classes, you want to cover for the following scenarios to ensure your code behaves as expected.

Single action

Test to verify that a single record produces the correct, expected result.

Bulk actions

Any Apex code, whether a trigger, a class or an extension, may be invoked for 1 to 200 records. You must test not only the single record case, but the bulk cases as well. These are often referred as bulk tests.

Positive behavior

Test to verify that the expected behavior occurs through every expected permutation, that is, that the user filled out everything correctly and did not go past the limits. These are called positive tests.

Positive tests are used to validate that your code is working correctly when given valid input. For example, you might write a positive test to verify that a method correctly calculates the total cost of an order when given a valid list of line items.

Negative behavior

There are likely limits to your applications, such as not being able to add a future date, not being able to specify a negative amount, and so on. You must test for the negative case and verify that the error messages are correctly produced as well as for the positive, within the limits cases. These are called negative tests.

Negative tests, also known as exception tests, are used to validate that your code is handling invalid input correctly. For example, you might write a negative test to verify that a method throws an exception when given an empty list of line items.

Restricted user

Test whether a user with restricted access to the sObjects used in your code sees the expected behavior. That is, whether they can run the code or receive error messages. These are also sometimes referred as permission based tests or runAs tests.

System.runAs() tests: The System.runAs() method allows you to test your code using different user permissions. This is useful for testing triggers and other code that may behave differently depending on the user’s permissions. For example, you might write a System.runAs() test to verify that a trigger only fires for users with the correct permissions.

Here’s an example of a positive test for a simple Apex method that calculates the total cost of an order:

@isTest
public static void testCalculateTotalCost() {
// Arrange
List<OrderLineItem> lineItems = new List<OrderLineItem> {
new OrderLineItem(Quantity = 3, Price = 10.0),
new OrderLineItem(Quantity = 2, Price = 20.0)
};

// Act
Decimal totalCost = OrderUtils.calculateTotalCost(lineItems);

// Assert
System.assertEquals(70.0, totalCost, 'Total cost should be correct');
}

And here’s an example of a negative test for the same method:

@isTest
public static void testCalculateTotalCost_EmptyLineItems() {
// Arrange
List<OrderLineItem> lineItems = new List<OrderLineItem>();

// Act and assert
Test.assertThrows(IllegalArgumentException.class,
() -> OrderUtils.calculateTotalCost(lineItems),
'Empty line items should throw an exception');
}

Advance Types of Tests

Apart from “what to test” in your code above,

There are certain unique challenges presented by certain advanced classes that utilize interfaces relating to asynchoronous, batch apex, webservice callouts etc.

Those require following techniques.

  1. Web service mock tests: Web service mock tests allow you to test Apex code that calls a web service by simulating the web service’s response. This is useful for testing code that relies on external services without actually making a call to the service. For example, you might write a web service mock test to verify that your Apex code can correctly handle a web service’s response when it returns a particular error code.

2. Batch Apex and asynchronous Apex can be a bit more complex than testing synchronous Apex code, as these types of code can run asynchronously and may have complex dependencies. Here are some best practices for designing test classes to test batch Apex and asynchronous Apex:

Use the Test.startTest() and Test.stopTest() methods to control the execution of asynchronous code: These methods allow you to specify a block of code that should be executed asynchronously, and they ensure that any asynchronous processes started within that block are completed before the test method ends.

Use the System.assert() methods to validate the results of your batch or asynchronous Apex code: The System.assert() methods can be used to validate that your batch or asynchronous Apex code is producing the correct results.

Use the Test.isRunningTest() method to bypass or modify the behavior of your batch or asynchronous Apex code in test methods.

The Test.isRunningTest() method returns true when a test method is executing and false in a production environment. You can use this method to bypass or modify the behavior of your batch or asynchronous Apex code in test methods to make it easier to test.

Use the AsyncApexJob and BatchApexJob objects to check the status of your batch or asynchronous Apex jobs: These objects allow you to check the status of your batch or asynchronous Apex jobs and ensure that they are completing successfully.

Here’s an example of a test method for a batch Apex class that processes a list of accounts:

@isTest
private static void testBatchAccountProcessor() {
// Arrange
List<Account> accounts = new List<Account> {
new Account(Name = 'Test Account 1'),
new Account(Name = 'Test Account 2')
};
insert accounts;

Test.startTest();
BatchAccountProcessor batch = new BatchAccountProcessor(accounts);
ID batchId = Database.executeBatch(batch);
Test.stopTest();

// Act
AsyncApexJob job = [SELECT Status FROM AsyncApexJob WHERE Id = :batchId];

// Assert
System.assertEquals('Completed', job.Status, 'Batch job should complete successfully');
}

Useful References

--

--

Mukundan

Technology enthusiast. I like to share my experience, learning and experiments on variety of tech topics. Salesforce, Cloud, AI, Machine Learning, Web3