Unit Testing with Apex
Heroku and Salesforce - From Idea to App, Part 11. Combining Salesforce with Heroku to build an “eCars” app, following a series of Trailhead Live video sessions
Join the DZone community and get the full member experience.Join For Free
This is the eleventh article documenting what I’ve learned from a series of 13 Trailhead Live video sessions on Modern App Development on Salesforce and Heroku. In these articles, we’re focusing on how to combine Salesforce with Heroku to build an “eCars” app—a sales and service application for a fictitious electric car company (“Pulsar”) that allows users to customize and buy cars, service techs to view live diagnostic info from the car, and more. In case you missed my previous article, you can find it here.
Just as a quick reminder: I’ve been following this Trailhead Live video series to brush up and stay current on the latest app development trends on these platforms that are key for my career and business. I’ll be sharing each step for building the app, what I’ve learned, and my thoughts from each session. These series reviews are both for my own edification as well as for others who might benefit from this content.
The Trailhead Live sessions and schedule can be found here:
The Trailhead Live sessions I’m writing about can also be found at the links below:
Last time we explored connecting microservices hosted on Heroku with Salesforce to offload high-cost operations and processes. This time we are focusing mainly on a Salesforce-specific topic, but one that should be used for any kind of application development process: unit testing.
For some, automated unit testing is an essential part of the application development process and a key component to making sure that code is running and behaving as expected. For some (and I’ve been guilty of this in the past), unit tests in Salesforce is that annoying gatekeeper that stops me from deploying my custom APEX code until I’ve satisfied the 75% code coverage requirement. Hopefully, after this session, we can change the perception of unit tests as a tool that greatly helps with our application development and maintenance processes as opposed to some kind of adversary.
A Primer on Unit Testing
What is “unit testing” exactly and what’s different about unit testing versus other testing methods like regression testing or smoke testing?
The “unit” part refers to the fact that the testing is verifying that a discrete method in the code, functionality or automation is working as intended separately from other dependencies. So if you have a trigger handler with several methods, you might write unit tests that target those specific individual methods as opposed to testing the entire class.
The “testing” part is self-explanatory and just refers to the fact that you’re making sure that the method in code, functionality, or automation is working as expected.
Unit tests in a nutshell
Here are some valuable benefits of unit tests:
- Identify bugs easily and detect them earlier
- Reduces the cost of fixing bugs and QA efforts
- Makes code design and refactoring easier
- Acts as a self-documenting set of test cases
However, these benefits are only fully realized when unit testing is implemented correctly and thoughtfully. Oftentimes, I come across cases where unit testing has been implemented as an afterthought, only implemented with the bare minimum requirements to actually deploy the code, or in some of the worst-offending cases, completing cheating the process.
A Framework for Unit Testing
The framework we’re basing our unit test design here is the “AAA” or Arrange, Act, and Assert framework. This framework doesn’t just apply to Salesforce code but can be used with any programming framework that supports unit tests.
The Salesforce examples for these would be something like as follows:
- Arrange: Test data setup and inserting the objects/records needed for the unit test
- Act: Call your methods or perform DML statements to run the code you want to test and capture the result
- Assert: Create assert statements comparing the results you captured with expected results. You typically want the test to error out or notify you in some way if the result you captured deviates from the expected result.
Unit testing is a core part of the Salesforce platform and Salesforce actually has specific requirements around unit tests and code coverage for deploying custom code to a production environment. Some Salesforce-specific rules about testing are as follows:
Here are several additional details on the above bullet points as well.
It’s nice that Salesforce actually uses its customers’ unit tests to help run regressions before each of their major releases since Salesforce instances can be highly customized with APEX code. A release breaking my existing code has been exceedingly rare and a nice feature of APEX classes is that you can actually set the API versions of them to be on a prior Salesforce release and then only upgrade them to the latest version when you have also fully tested things on your own.
This 75% code coverage requirement means that if you have 1000 lines of APEX code across the entire org, APEX test classes must simulate and run at least 750 of those lines of code in order for code updates or new code to be deployed. This is where I’ve seen some pretty clever workarounds (read: cheats) that I won’t share, but just know that they should never be used and you should always strive to write legitimate and well-thought-out unit tests.
Having a variety of test cases makes the unit tests much more valuable since you are covering more edge cases with them. As a result, they can alert you if some change to the system is introduced that impacts the code from performing as expected.
- Positive tests - is the code doing the right thing when it’s supposed to?
- Negative tests - is the code not doing the thing when it’s NOT supposed to?
- Bulk tests - does the code still run as expected if a large chunk of records updates at once? Or do we hit some kind of CPU time-out error or SOQL query limit error?
- Profile/permission tests - does the code run as expected when performed by users with different profiles and permissions? Or is there some dependency created when a different profile tries to run it?
Finally, having a “Datafactory” class is a huge time-saver because having one place where valid test data can be generated and called by other test classes will come in handy. For example, adding a new required field or validation to the system often causes unit tests to break because the test data does not account for the new field or validation rule. Having a single place where you can update this saves you the trouble of having to change the test data setup for each of your affected unit test classes.
Creating an Apex Unit Test for the eCars App
For the eCars app that we’re building, the activity will be to write an APEX test class for the PdfCreateService that we used in the prior session to call a Heroku microservice for generating PDF documents. The eCars code can be found at the linked Github repo and I have also included a link to the specific PdfCreateService class. If you have not gone through the exercise of pulling the code into a Salesforce Developer org or a scratch org, you’ll want to review some of the previous articles to take that pre-requisite step or follow the instructions in the Github repo on how to clone and deploy the application.
You’ll see when examining the PdfCreateService class, it’s a pretty simple class with really one method, invokePdfCreateService, which takes a single parameter of a custom child class called ServiceInput and then makes an outbound HTTP callout to an external Heroku service. The ServiceInput child class is also used to conveniently serialize JSON data for the Heroku service.
The first step of creating the unit test is to create a testDataFactory class to help generate the test data for the unit test. This isn’t included in the Github repo as of today, so you’ll need to write one yourself, follow along in the video to create one, or grab it from the solutions sheet that is posted in the Chatter discussion group linked at the end of this article. The testDataFactory class will need to be able to create a test Lead, a test User, and also assign the test User the necessary permission set for the eCars app.
We chose a bit of an interesting class to unit test because this class has a method that makes a webservice callout to an external service. It would be quite a problem if our unit test actually made a real webservice callout to the external service for two major reasons. First, how would the external service know that it’s just a test and not a real callout and behave accordingly? Second, we don’t know how the real external service might behave at any given moment like for example if there is service disruption and it’s down exactly when we are running our tests.
As a result, Salesforce provides a way to actually create something called an HTTPMock class that is specifically used for testing methods that make web service callouts so that the expected behavior from the web service can be simulated or “mocked” in the HTTPMock class specifically for unit testing use cases. There’s a specific interface called HttpCalloutMock that needs to be implemented as shown below.
The goal for the eCars app would be to create a PdfCreateServiceMock class that we can use in our test classes to return a variety of HTTP responses and payloads. In the video, Mohith provides an example of this but you should try to create your own version that might handle a number of other cases and responses.
The last step, once we have our testDataFactory class and PdfCreateServiceMock class, is to create the actual TestPdfCreateService test class that will utilize both of the prior classes. The test class should:
- Create test data using our testDataFactory class, including one or more test users with different relevant profiles, as well as one or more test Lead records for running our test with
- Assign permission sets to the user(s)
- Have one or more test methods that run as our test users using the system.runAs method
- Set data for the PdfCreateService.ServiceInput child class
- Invoke the PdfCreateService.invokePdfCreateService with the ServiceInput object set previously
- Call the PdfCreateServiceMock with the desired mocked up response
- Run system.Assert statements to assert that the results from running the code match up to our expected results
If all of the above criteria are fulfilled, once you run your test class using VSCode’s handy “runTest” command (comes with the Salesforce tools VSCode extension), you should get something like 90%+ test coverage. Inspecting the classes in VSCode or the developer console will show which lines of code were covered in the test run vs. which ones were not.
If you weren’t able to get your test class built and solved on your own, that’s fine. A solution for this should be posted in the Chatter discussion forum or you can ask for help there if you are stuck.
As mentioned early on, unit testing in my opinion is one of the most underutilized and underappreciated components of building apps on the Salesforce platform. I often see a great deal of technical debt that is built up that revolves around unit tests and APEX test classes. It’s gotten to the point where if I’m jumping into a project where some coding has already been done, the first thing I do is run all of the org’s APEX test classes and see how many of the unit tests fail or provide insufficient coverage to certain classes. I think this happens because too many people approach unit tests as a kind of barrier or box that you just have to check in order to get your code to deploy, but it doesn’t have to be this way. If more developers approached unit tests as a helpful and core part of the code that they’re developing, well-built and designed unit tests can actually end up saving on technical debt.
For more information and advanced topics related to unit testing, check out the links to the resources below:
- Unit Testing On the Lightning Platform Trailhead Module
- Apex Testing Trailhead Module
- Building a Mocking Framework with the Stub API
- Advanced Apex Testing Blog
- Testing Apex | Apex Developer Guide
- Apex Recipes
In the next article, we’re going to go through the application package and deployments.
If you haven’t already joined the official Chatter group for this series, I certainly recommend you do so. That way, you can get the full value of the experience and also pose questions and start discussions with the group. Oftentimes, there are valuable discussions and additional references available there such as the slides from the presentation and links to other resources and references.
About me: I’m an 11x certified Salesforce professional who’s been running my own Salesforce consultancy for several years. If you’re curious about my backstory on accidentally turning into a developer and even competing on stage on a quiz show at one of the Salesforce conventions, you can read this article I wrote for the Salesforce blog a few years ago.
Thanks to Jason Sun for his kind permission to publish this article.
Opinions expressed by DZone contributors are their own.