進行状況の追跡を始めよう
Trailhead のホーム
Trailhead のホーム

Write Negative Tests

Learning Objectives

After completing this unit, you’ll be able to:

  • Explain what negative unit tests are.
  • Write negative unit tests.

Why Write Negative Unit Tests?

Negative testing sounds so… negative. But many developers are positive that negative testing is crucial. Check out this video for background on the what and why of negative testing.


Negative unit tests are useful for more than just making sure that every line of code is tested. Indeed, they’re sometimes more important than positive tests. Negative tests demonstrate that your code properly handles invalid data, unexpected user input, and changes in other parts of the code base. More important, negative tests affirm that the code you’ve written is fault-tolerant. 

Testing how your code handles exceptions is as important as making sure your code works as intended during valid conditions. Imagine a batch process. Every night at 2 AM, your code processes account updates that your contacts have entered via the customer community. If the code fails on the first record in the batch, and it’s not gracefully handling exceptions, the code won’t process the remaining records. Negative testing helps ensure situations like that don’t happen.

The Pattern

Positive testing tests your code with valid inputs to demonstrate what happens when your code functions properly. Negative testing demonstrates that your code properly handles invalid data and exceptions. That is not always easy to accomplish. After all, throwing an exception during a unit test causes that test to fail. 

So how can you write tests that pass when the code throws an exception? More importantly, because a number of exception types are available, how can you write tests that pass only when the code throws the expected type of exception? The general pattern for negative tests looks like the following.

Negative testing pattern

  1. Generate or load your test data.
  2. Call Test.startTest().
  3. Create and set a Boolean variable to false.
  4. Start a try/catch block.
  5. Execute your code.
  6. Catch the expected exception, and if the details match, set the Boolean variable to true.
  7. Assert that the Boolean variable is true.

In our AccountWrapper class, we have a line of code that throws an exception. Here’s what a test exercising that would look like. 

  1. Click Setup icon  and select Developer Console.
  2. Select File > Open > AccountWrapper_Tests.apxc.
  3. Before the end of your class add the following code.
    @isTest static void testNegativeAccountWrapperRoundedOpps(){
      List<Account> accts = [SELECT Id FROM Account];
      List<Opportunity> opps = [SELECT ID, Amount FROM Opportunity WHERE accountId in :accts];
      List<AccountWrapper> wrappers = new List<AccountWrapper>();
      for(Opportunity o : opps){
        o.amount = 0;
      }
      update opps;
      for(Account a : accts){
        wrappers.add(new AccountWrapper(a));
      }
      Test.startTest();
      List<Boolean> exceptions = new List<Boolean>();
      for(AccountWrapper a : wrappers){
        try{
          a.getRoundedAvgPriceOfOpps();
        } catch (AccountWrapper.AWException awe){
          if(awe.getMessage().equalsIgnoreCase('no won opportunities')){
            exceptions.add(true);
          }
        }
      }
      Test.stopTest();
      for(Boolean b : exceptions){
        system.assert(b, 'Account should have thrown an exception');
      }
    }
  4. Click File > Save, then Test > New Run.
  5. Select AccountWrapper_Tests, then select testNegativeAccountWrapperRoundedOpps.
  6. Click Run.

Code Highlights

In this test, you’re building on the opportunities that your @testSetup method created. However, in order to test how AccountWrapper.apxc handles invalid data, you need to ensure that the opportunities created by the @testSetup method all have an amount of 0. To do this, the test uses a for-loop to iterate over each opportunity and set the amount field to 0.  With the test data set up this way, the AccountWrapper’s getRoundedAvgPriceOfOpps() method calculates a rounded price of 0 for all opportunities. This causes the code to throw an AWException, a custom exception type defined in the AccountWrapper.apxc It’s this AWException object that you catch in the unit test.

The thing about try/catch blocks like this, however, is that if they’re written badly, the catch block can catch any instance, or subclass, of exception. The best practice is that they catch only specific subclasses of exception. That’s what you’re doing in the code above. However, once you catch the exception, it’s important to have the test check the exception object to ensure not only that it is the expected type of exception, but also that its message and details are what you expect. In this case, you’re expecting a custom exception type of AWException, which is defined in our AccountWrapper.apxc. 

Often code has multiple places where it can throw an exception—sometimes even the same type of exception. For instance, a ContactService class with a validate method might throw a ContactServiceException in three or four places and for different reasons. If we fail to check the type, message, and details in our test, we risk our tests incorrectly passing. This is called a false positive, and too many of these can erode trust in your tests.