SoC and the Apex Common Library Tutorial Series Part 15: The Difference Between Unit Tests and Integration Tests

What is Unit Testing?

Unit Testing, is a way to test the logic in one class and ONLY one class at a time. Most classes in your system at some point do one of the following:

1) Call to another class to get some work done.
2) Query the database directly in the class.
3) Do DML transactions directly in the class.

However, in unit testing, we ideally don’t want to do any of those things, we don’t care if the querying or the DML transactions or the calls to those other classes work, because what we want to test is that the logic of our class works in every theoretical permutation we can think of AND THAT’S IT! We just want to know if our logic, that we designed for this one class actually works as we anticipate it to work.

You might be thinking, “How is that possible? We need those queries and those dml statements and those classes to successfully run our code!”. Well that my friends is simply not true. With the implementation of separation of concerns and by leveraging one of the many available mocking frameworks we can fake all of those things to build true unit tests.


When Should I use Unit Testing?

Just because you are leveraging unit testing doesn’t mean there still isn’t a need for integration tests (more on that below), so when should you do Unit Tests in favor of Integration tests? It’s a pretty simple answer. The logic in your class’s code could have 40+ paths it could take… maybe even 100… The code in your class could have logic that runs when your code fails, logic that runs when your codes successful, logic that runs when someone creates a $200,000,000 opportunity as opposed to a $2,000 opportunity. There could be so many paths. We DO NOT need an integration test for every single one of those paths. It could take 20+ minutes to run integration tests for all those paths whereas unit testing them could take just seconds. As your codebase grows, if you didn’t make these unit tests your test class runs to deploy to prod could take hours… maybe days… we don’t want that homie. So when do you unit test? You unit test to test the bajillions of permutations of your class’s logic. When do we choose integration tests?? Keep reading. You’ll find out below.

An additional benefit to unit testing/mocking responses is testing hard to test or impossible to test error catching scenarios. One common scenario that I find myself frequently building error catching around is record locking… but how on earth can you test that with real data at run time? It’s borderline impossible, however unit testing makes testing for errors a breeze. It’s so easy you’ll cry, lol, the dream of 100% code coverage can go from just a dream to a very real and easily obtainable thing with unit tests and mocking.


What is Integration Testing?

Integration testing is when you test your code from point A all the way to point Z. What I mean by that is, you actually have your code in your test classes doing SOQL statements, DML transactions, calling the other classes it depends on, etc. Basically your code is truly calling all the real logic from beginning to end, no mocking or fake return results anywhere. Integration testing is what 90%+ of Salesforce orgs seem to do 100% of the time and that is one dangerous game to play. The majority of your tests should not be integration tests… maybe like 20% of them or so. If 100% of your test classes are integration tests, over time you’ll suffer from long test execution and deployment times (or alternatively poorly tested code to reduce those times). If you’ve ever been stuck in a 6+ hour deployment, I think you know what I mean… and what if one test fails during that deploy?… it really hurts, lol. Please, don’t get me wrong though, you can not replace integration tests with unit tests, they are of equal importance, it’s just important that you only use integration tests when needed.


When Should I use Integration Testing?

All apex classes should have some level of integration testing. I like to write somewhere around 20% of my tests as integration tests and 80% of my tests for a class as unit tests. Typically I will, for each class that has dependencies (classes that the class I’m currently testing calls) write two integration tests for each dependency. One integration test that tests a successful path through my classes code and its dependencies and another that tests failures. This may be, in some peoples opinion, overkill, but I personally feel that it’s not an enormous amount of overhead (typically) and it gives me some piece of mind that all of the classes my class I’m testing depends on still operate well with it. So, when to use integration testing? In my opinion, in every test class, but use them sparingly, use them to check that your transactions between classes still work, don’t use them to test every logical path in your class’s code. That will spiral out of control.


Next Section

Part 16: Unit Test Mocks with Separation of Concerns

SoC and the Apex Common Library Tutorial Series Part 16: Unit Test Mocks with Separation of Concerns

How does Unit Testing fit into Separation of Concerns?

The answer to this is simple, without Separation of Concerns, there is no unit testing, it just simply isn’t possible. To unit test you need to be able to create stub (mock) classes to send into your class you are testing via dependency injection (or through the use of a factory, more on this in the next section). If all of your concerns are in one class (DML transactions, SOQL queries, service method, domain methods, etc) you cannot fake anything. Let’s take a look at a couple examples to illustrate this problem:

Unit Testing a class with SoC Implemented

//This is the class we would be testing
public with sharing class SoC_Class
{
	private DomainClass domainLayerClass;
	private SelectorClass selectorLayerClass;

	public SoC_Class(){
		//This is calling our private constructor below
		this(new domainLayerClass(), new selectorLayerClass());
	}

	//Using a private constructor here so our test class can pass in dependencies we would
	//like to mock in our unit tests
	@TestVisible
	private SoC_Class(DomainClass domainLayerClass, SelectorClass selectorLayerClass){
		this.domainLayerClass = domainLayerClass;
		this.selectorLayerClass = selectorLayerClass;
	}

	public List<Case> updateCases(Set<Id> objectIds){
		//Because of our dependency injection in the private constructor above we can 
                //mock the results of these class calls.
		List<Case> objList = selectorLayerClass.selectByIds(objectIds);
		if(!objList.isEmpty()){
			List<Case> objList = domainLayerClass.updateCases(objList);
			return objList;
		}
		else{
			throw new Custom_Exception();
		}
	}
}

//This is the class that we build to unit test the class above
@IsTest
public with sharing class SoC_Class_Test
{
	@IsTest
	private static void updateCases_OppListResults_UnitTest(){
		//Creating a new fake case id using the IdGenerator class. We do this
		//to avoid unnecessary dml insert statements. Note how the same id is used 
                //everywhere.
		Id mockCaseId = fflib_IDGenerator.generate(Case.SObjectType);
		//Creating a set of ids that we pass to our methods.
		Set<Id> caseIds = new Set<Id>{mockCaseId};
		//Creating the list of cases we'll return from our selector method
		List<Case> caseList = new List<Case>{new Case(Id = mockCaseId, Subject = 'Hi', 
                Status = 'New', Origin = 'Email')};
		List<Case> updatedCaseList = new List<Case>{new Case(Id = mockCaseId, Subject 
                = 'Panther', Status = 'Chocolate', Origin = 'Email')};

		//Creating our mock class representations by using the ApexMocks class's mock 
                //method and passing it the appropriate class type.
		fflib_ApexMocks mocks = new fflib_ApexMocks();
		DomainClass mockDomain = (DomainClass) mocks.mock(DomainClass.class);
		SelectorClass mockSelector = (SelectorClass) mocks.mock(SelectorClass.class);

		//After you've setup your mocks above, we need to stub (or setup the expected
		//method calls and what they would return.
		mocks.startStubbing();

		//This is the actual selectByIds method that we call in the
		//createNewOpportunities method that we are testing
		//Here we are setting up the fake return result it will return.
		mocks.when(mockSelector.selectByIds(caseIds)).thenReturn(caseList);

		mocks.when(mockDomain.updateCases(caseList)).thenReturn(updatedCaseList);

		//When you are done setting these up, DO NOT FORGET to call the stopStubbing 
                //method or you're gonna waste hours of your life confused
		mocks.stopStubbing();

		Test.startTest();
		//Passing our mock classes into our private constructor
		List<Case> updatedCases = new SoC_Class(mockDomain, 
                mockSelector).updateCases(caseIds);
		Test.stopTest();

		System.assertEquals('Panther', updatedCases[0].Subject, 
                                    'Case subject not updated');
		//Verifying this method was never called, we didn't intend to call it, so
		//just checking we didn't
		((Cases)mocks.verify(mockDomain, mocks.never().description('This method was 
                called but it shouldn\'t have been'))).createOpportunities();
		//Checking that we did indeed call the createTasks method as expected.
		((Cases)mocks.verify(mockDomain)).updateCases(caseList);
	}
}

Above you can see we are passing in fake/mock classes to the class we are testing and staging fake return results for the class methods we are calling. Thanks to separating out our concerns this is possible. Let’s take a look at how impossible this is without SoC in place.

Unit Testing a class without SoC Implemented

//This is the class we would be testing
public with sharing class No_SoC_Class
{
	public List<Case> updateCases(Set<Id> objectIds){
		//Because of our dependency injection in the private constructor above we can 
                //mock the results of these class calls.
		List<Case> objList = [SELECT Id, Name FROM Case WHERE Id IN: objectIds]
		if(!objList.isEmpty()){
			for(Case cs: objList){
				cs.Subject = 'Panther';
				cs.Status = 'Chocolate';
			}

			update objList;
			return objList;
		}
		else{
			throw new Custom_Exception();
		}
	}
}

@IsTest
public with sharing class No_SoC_Class_Test
{
	@TestSetup
	private static void setupData(){
		Case newCase = new Case(Subject = 'Hi', Status = 'New', Origin = 'Email');
		insert newCase;
	}

	@IsTest
	private static void updateCases_CaseListResults_IntegrationTest(){
		Set<Id> caseIds = new Map<Id, SObject>([SELECT Id FROM Case]).keySet();
		List<Case> updatedCases = new No_SoC_Class().updateCases(caseIds);
		System.assertEquals('Panther', updatedCases[0].Subject, 'Case subject not 
                updated');
	}
}

You can see above we did no mocking… it wasn’t possible, we had no way of passing in fake/mock classes to this class at all so we had to do an integration test where we create real data and update real data. This test will run considerably slower.


How do I transition my Existing Code to start leveraging SoC so I can use Unit Test Mocking?

It’s not a simple path unfortunately, there is a lot of work ahead of you to start this transition, but it is possible. The key is to start small, if you are a tech lead the first thing you need to do is find the time to train your devs on what SoC and mocking is and why you would use it. It’s critical they understand the concepts before trying to roll something like this out. You cannot do everything as a lead even if you’d like to, you need to build your team’s skillset first. If you aren’t a lead, you first need to convince your lead why it’s critical you start taking steps in that direction and work to get them onboard with it. If they ignore you, you need a new lead… After accomplishing either the above you should do the following:

1) Frame your situation in a way that the business arm of your operation understands the importance of altering your code architecture to leverage SoC and unit testing. This is typically pretty easy, just inform them that by spending a few extra points per story to transition the code you are going to gain more robust testing (resulting in less manual tests) and that the code will become more flexible over time, allowing for easier feature additions to your org. Boom, done, product owner buy in has been solidified. Ok, lol, sometimes it’s not quite this easy, but you know your business people, give them something they won’t ignore, just be careful to not make yourself or your team sound incompetent, don’t overcommit and make promises you can’t keep and frame it in words business people understand (mostly dollar signs).

2) Start small, take this on a story by story basis. Say you have a new story to update some UI in your org, with business owner buy-in, tack on a few extra points to the story to switch it to start using SoC and Unit Testing. Then, MAKE SURE YOUR TESTS ARE INCREDIBLE AND YOU TRUST THEM! I say this because, if your tests are incredible you never have to ask for permission again… I mean think about it, wtf does the average business person know about code. As long as I don’t introduce any new bugs I could change half the code base and they’d never even know. If your tests are golden, your ability to change on a whim is as well. Make those test classes more trustworthy than your dog (or cat I guess… but I wouldn’t trust a cat).

3) Over time, your transition will be done… it might take a year, it might take 5 years, depends on how busted your codebase was to begin with. Hopefully, by the time you are done, you have an extremely extensible codebase with tests you can trust and you never have to ask for permission to do a damn thing again.


Additional Information

If the above didn’t make how to implement mocking via separation of concerns any clearer, I also have a video covering the subject here.


Next Section

Part 17: Implementing Mock Unit Testing with Apex Mocks