Unit Test a Salesforce REST API

This post is a continuation of the previous two posts where we created a Salesforce DX REST API project and then coded HTTP methods to perform CRUD operations. The full project is on GitHub. In this post we will cover some general unit testing concepts and specifically how to unit test HTTP methods in Apex.

Arrange, Act, Assert

There is a common pattern in unit tests called Arrange, Act, Assert. It states that each unit test does these three things.

  • Arrange: Setup the conditions for the test, including any test data.
  • Act: Execute the code under test.
  • Assert: Assert that the code behaved the way you expected.

I have seen a lot of Apex unit tests that arrange and act but don’t assert. They increase code coverage because they execute some Apex code but they don’t include asserts to verify that the code behaved as expected. In my opinion, it’s not a test if it doesn’t verify the behavior of the code under test and the way to do that is with meaningful assertions.

Testing HTTP Methods

Part of the challenge of unit testing REST APIs is the fact that we’re not actually making an HTTP request but we need to simulate the HTTP method, request URI, headers, etc. This is one of those times when it’s good to be a Salesforce developer! This can be a hassle on some platforms but Salesforce makes it painless.

To execute our HTTP method we need to do three things:

  1. Create a RestRequest object.
  2. Create a RestResponse object.
  3. Call our method.

RestRequest

First, assign the request property of the RestContext class to a new instance of a RestRequest object.RestContext.request = new RestRequest();

Then set whatever properties need to be set to call your HTTP method. At the very least this will be the requestURI and httpMethod properties but others may be necessary as well.

Create a Test Class

In VS Code go to the command palette Ctrl+Shift+P and select SFDX: Create Apex Class . I named mine HouseServiceTest. Then make your class public and annotate your class with @isTest to identify it as a test class.

Our first test is going to test the positive case for our GET method. If the method succeeds it will return status code 200 OK so I’m going to name it getShouldReturn200() .

First we annotate our method with @isTest to identify it as a test method, then we follow the Arrange, Act, Assert pattern.

Arrange

First we create a House object and save it to the database so we have something to attempt to fetch with our GET method. Then we setup the request and response objects as described above. Some of this could be done in a helper method to avoid repeating code but for this post each test contains its own setup.

Act

The Act section of the test is usually the shortest. For this test we just call the HouseService.getHousebyId() method. The Test.startTest() and Test.stopTest() calls that surround our test reset governor limits. For this test we’re acting on a single record so we don’t have to worry about that but I put them in every test out of habit.

Assert

In the assert section we need to verify that the code we are testing behaved as expected. If getHouseById() method worked as expected two things should be true:

  1. The response.statusCode should be 200 OK .
  2. The response.responseBody should include a House object.

To verify that the code worked as expected I attempt to deserialize the responseBody as a House object, check the status code and check that the Id of the deserialized response body matches the Id of House object we inserted into the database in the Arrange section. If any of those assertions fail, the test will fail.

Negative Test

The second test will test the negative case for the GET method. If our GET method can’t find a record in the database that matches the Id parameter then it should return 404 Not Found . So this test will look a lot like the last test except we’ll call the GET method with an Id that we know doesn’t exist in the database. We’ll call this method getWithInvalidIdShouldReturn404() .

This test only has a couple of differences from the positive test:

  1. The request URI includes an Id parameter of ‘abcdefg’ which obviously won’t exist in the database.
  2. The assert verifies that the status code is 404 Not Found .

Run Tests in VS Code

I really like how simple it is to run tests in VS Code. After you push the code to your scratch org click on the Run Test link above each unit test to run that test. Or click the Run All Tests link at the top of your test class to run them all.

Run tests in VS Code

Code Coverage in VS Code

You can also see code coverage in VS Code but it requires a little setup. Open your settings.json file. On Windows this file is in \AppData\Roaming\Code\User\settings.json. You can also get to it by selecting File > Preferences > Settings. Add the following line: “salesforcedx-vscode-core.retrieve-test-code-coverage”: true

Then click the icon at the bottom of the VS Code window that says Highlight Apex Code Coverage. It looks like this when selected:

Now when you run your tests from VS Code you will see coverage in the Salesforce CLI output.

Code coverage summary in VS Code

You can also see which lines of code are covered in the editor.

How Much Code Coverage?

We have to have a minimum of 75% code coverage to deploy to production but how much should we actually target? I think it depends. Often there is a trade-off between getting higher levels of code coverage and adding test specific code to your production code. Also, you can reach a point of diminishing returns. In general, I try to get as much code coverage as I can without introducing test specific code into my production code.

For example, you can see in the images above that the code that catches generic exceptions is uncovered. We could add a static property to our class that we only set in tests and if that property is set we could intentionally throw an exception.

That’s a valid approach but I think it should be considered on a case-by-case basis. In this case it could get us to 100% code coverage. However, I don’t think it provides any benefit but it does introduce some small risk that the property could be set, and the exception thrown by mistake, in production code. Given the trade-offs I’m happy with 82% coverage in this case.

Conclusion

In this post we covered:

  • The Arrange, Act, Asset pattern
  • How to test HTTP methods
  • Naming conventions for unit tests
  • Positive and negative test cases
  • Test execution and code coverage in VS Code
  • Code coverage trade-offs

In the next post we’ll wrap this up by testing our API with my favorite REST API testing tool, Postman.

Leave a Comment