0

Is a bad practice call the same method many times in the same test method? Example:

@isTest static void testSelectDevice{

 List <Product2> device1 = new List<Product2>();
 List <Product2> device2 = new List<Product2>();
 List <Product2> device3 = new List<Product2>();

 Test.startTest();
 device1 = DeviceClass.selectDevice('Model1', 'Brand1');
 device2 = DeviceClass.selectDevice(null, 'Brand1');
 device3 = DeviceClass.selectDevice('Model1', null);
Test.stopTest();
}

Can I do this way or is it better to create more two methods for device2 and device3?

5

Is a bad practice call the same method many times in the same test method?

It'd be better to test one thing per test method and to appropriately name them based on what specific scenario they're testing.

The Get Started with Apex Unit Tests has the following example

@isTest
private class TemperatureConverterTest {
    @isTest static void testWarmTemp() {
        Decimal celsius = TemperatureConverter.FahrenheitToCelsius(70);
        System.assertEquals(21.11,celsius);
    }
    
    @isTest static void testFreezingPoint() {
        Decimal celsius = TemperatureConverter.FahrenheitToCelsius(32);
        System.assertEquals(0,celsius);
    }
    @isTest static void testBoilingPoint() {
        Decimal celsius = TemperatureConverter.FahrenheitToCelsius(212);        
        System.assertEquals(100,celsius,'Boiling point temperature is not expected.');
    } 
    
    @isTest static void testNegativeTemp() {
        Decimal celsius = TemperatureConverter.FahrenheitToCelsius(-10);
        System.assertEquals(-23.33,celsius);
    }
      
}

That's generally the goal with unit tests:

  • Appropriately name them - there's many conventions, but it helps to know what is going on without reading/understanding every line in the test
  • Have them test one thing
  • Have a meaningful assert (with a message) to confirm the expected behavior

Why is all that important?

  • Easier to understand
  • Easier to read
  • Easier to fix/catch bugs

If your example fails (ignoring you have no assertions which you should have), how do you know what exactly fails if you've tested 3 different paths in your code in one method?

By having it test one thing, appropriately named, with assertions with an assertion message, - it'll save you time and point to one specific problem in your code. It makes your life easier and certainly anyone new to the code in the future to understand your features/use-cases.

1
  • 1
    On the flip side, though, if you have too many tests, you might run into governor limits, like the daily unit test limit, or the global SOQL and DML per unit test run (300,000 and 400,000, I believe).
    – sfdcfox
    Nov 3 '21 at 15:39
4

You can do this, and in some cases, it may be unavoidable. There is a daily unit test limit per org, as stated in the documentation:

All Apex tests that are started from the Salesforce user interface (including the Developer Console) run asynchronously and in parallel. Apex test classes are placed in the Apex job queue for execution. The maximum number of test classes that you can run per 24-hour period is the greater of 500 or 10 multiplied by the number of test classes in the org. For sandbox and Developer Edition organizations, this limit is higher and is the greater of 500 or 20 multiplied by the number of test classes in the org.

(Emphasis mine)

That said, it is generally considered a best practice to test just one thing per unit test. If that "one thing" is a controller, you may very well end up calling it several times in a single unit test. Avoid mixing unrelated objects in the same test, and make sure you always add assertions to verify that your logic is correct.

2

This may be heresy but ...

In my experience, this is a matter of scope/corpus of your regression test codebase.

Using the example cited by @KrisGoncalves, I would have written one testmethod

@IsTest
static void givenTempsVerifyConversions() {
   System.assertEquals(21.11,TemperatureConverter.FahrenheitToCelsius(70),'warm temp sb betw 0 and 100');
   System.assertEquals(0,TemperatureConverter.FahrenheitToCelsius(32),'32F is 0C');
   System.assertEquals(100,TemperatureConverter.FahrenheitToCelsius(212),'212F is 100C');
   System.assertEquals(-22.33,TemperatureConverter.FahrenheitToCelsius(-10),'negative F is always negative C');
}

Why - because the code under test is trivial and consolidating all of the use cases in one testmethod is a nice encapsulation. This is particularly true when you have a large body of code under test and might have hundreds if not thousands of testmethods. The third assert argument tells you why a test failed and you will also get the line number

Now, for more complex code under test, if the code under test takes a singleton argument (say, a single recordId or sobject or apexType), I would adopt the one testmethod per use case. I would name it following the givenWhenThen convention.

@IsTest
static void givenOpenOpportunityWhenQuotedThenVerifyLinked() {
}

If my code under test takes a collection, then I would do the following:

The example also follows the fflib pattern of domain-service-selector-unitOfWork; here, I use a domain

@IsTest
static void givenOpportunitiesWhenNewVerifyValidations() {
   // Given Opportunities with various data conditions
   Opportunity[] mockOppos = new List<Opportunity> {
      new Opportunity(FldA= .., FldB = ..),   // [0] stage and type in conflict
      new Opportunity(FldA= .., FldB = ..),   // [1] stage and type not in conflict
      new Opportunity(FldA= .., FldD = ..),   // [2] stage and amount in conflict
      new Opportunity(FldA= .., FldD = ..)   // [3] stage and amount not in conflict
  };

  // given domain
  IOpportunities domain = Opportunities.newInstance(mockOppos);        

  // When  New recs Verified
  domain.onValidate();

  // Then verify
  System.assertEquals(true,mockOppos[0].hasErrors(),'[0] is in stage/type conflict');
  System.assertEquals(false,mockOppos[1].hasErrors(),'[1] is not in stage/type conflict');
  System.assertEquals(true,mockOppos[2].hasErrors(),'[2] is in stage/amount conflict');
  System.assertEquals(false,mockOppos[3].hasErrors(),'[3] is not in stage/amount conflict');

  // Then verify error messages for [0] and [2]

}

One advantage to the above is that bulk handling is implicitly tested; a disadvantage is the need to keep your collection indices straight - especially as they are susceptible to copy-paste errors. Hence use of the third assert argument is a must.

Of course, for service layers, you may have several testmethods for each of the service methods depending on how complicated the service is and how unique the mocked environment needs to be. Of course, complicated service methods should be broken down into smaller methods that can be unit tested separately so your mock setup can be simpler once you know the components work perfectly.

As others have said, learning to write code that is easily testable is a skill to learn. The more daunting the testmethod writing looks, the more likely it means your code under test needs refactoring to make the pieces individually testable.

And, adopting patterns like fflib allow for the introduction of dependency injections (see ApexMocks) of mocked selectors, services, domains, and units of work - allowing you to focus on just testing the code under test without having to construct elaborate DML'd sobjects. Tests run faster

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.