Advanced 45 min read

Write the Test Suite

By the end of this lesson, you'll be able to:

  • Write bulk, negative-path, and async test coverage across the full layered architecture
  • Use dependency injection to unit test service-layer logic without any database records
  • Apply Module 31's complete testing toolkit one final time, at full project scale

Prerequisites: "Secure the Application"

A bulk trigger test

@isTest
private class MilestoneTriggerTest {
    @isTest
    static void allowsValidStatusTransitionsInBulk() {
        Project__c proj = new Project__c(Name = 'Test Project', Status__c = 'Active');
        insert proj;

        List<Milestone__c> milestones = new List<Milestone__c>();
        for (Integer i = 0; i < 200; i++) {
            milestones.add(new Milestone__c(Project__c = proj.Id, Status__c = 'Not Started'));
        }
        insert milestones;

        for (Milestone__c m : milestones) m.Status__c = 'In Progress';

        Test.startTest();
        update milestones;
        Test.stopTest();

        for (Milestone__c m : [SELECT Status__c FROM Milestone__c WHERE Project__c = :proj.Id]) {
            Assert.areEqual('In Progress', m.Status__c);
        }
    }
}

200 Milestones updated in one bulk operation — Module 31\'s "always test at bulk scale" rule, still the very first check for any trigger-bearing object.

A dependency-injected service unit test, with zero DML

@isTest
static void marksProjectAtRiskWhenMilestoneOverdue() {
    Id fakeProjectId = TestHelper.fakeId(Project__c.SObjectType);
    Milestone__c overdue = new Milestone__c(
        Id = TestHelper.fakeId(Milestone__c.SObjectType),
        Project__c = fakeProjectId,
        Status__c = 'In Progress',
        Due_Date__c = Date.today().addDays(-3)
    );

    FakeMilestoneSelector fakeSelector = new FakeMilestoneSelector(new List<Milestone__c>{overdue});
    ProjectHealthService service = new ProjectHealthService(fakeSelector);

    Map<Id, String> result = service.calculateHealth(new Set<Id>{fakeProjectId});

    Assert.areEqual('At Risk', result.get(fakeProjectId));
}

This is Lesson 5\'s dependency injection paying off directly in the test suite — ProjectHealthService\'s core business rule is verified without inserting a single real record, running dramatically faster than a DML-based test would.

An async job test

@isTest
static void recalculatesHealthForAllActiveProjects() {
    // ... insert an Active Project with an overdue Milestone

    Test.startTest();
    Database.executeBatch(new RecalculateProjectHealthBatch());
    Test.stopTest(); // forces the batch to actually run

    // assert the Project's Health__c field was updated correctly
}

Test.startTest()/Test.stopTest() forcing the batch to run synchronously within the test — Module 31\'s and Module 38\'s exact async-testing pattern, reused one final time for this project\'s nightly safety net.

Exercise

As a comment, explain why marksProjectAtRiskWhenMilestoneOverdue can use TestHelper.fakeId() instead of actually inserting a Project__c and Milestone__c record.

Show hint

Think about what ProjectHealthService actually depends on, thanks to Lesson 5's design.

APEX

Write the Test Suite Quiz

1. What makes the dependency-injected service test meaningfully faster than a DML-based test would be?

Log in to submit the quiz and save your score.

My Notes

Log in to keep private notes on this lesson.

Questions about this lesson

No questions yet — be the first to ask.

Log in to ask a question about this lesson.

Summary

This lesson applies Module 31's complete testing toolkit across every layer of this project — bulk trigger tests, dependency-injected service unit tests, and async job tests — the same rigor from every prior project module, now spanning four objects and several architectural layers at once.