The Limitations of TestDataFactory and Selector Pattern: Two Fundamental Problems

8 min read
SalesforceApexTestingArchitectureClean CodeTestDataFactorySelector PatternQuery DelegationApexEloquentTechnical Debt
An in-depth analysis of the structural limitations inherent in TestDataFactory and Selector Pattern, and how Query Delegation Pattern with Apex Eloquent provides a sustainable solution.

In Salesforce, it's recommended to use TestDataFactory for explicitly creating test data in Apex tests. TestDataFactory aims to centrally manage test data through utility classes, improving reusability and enhancing test code readability.

🔄 Two Usage Styles of TestDataFactory

While TestDataFactory is convenient, when dealing with complex data structures, it typically falls into two styles:

  1. Providing Factory methods that generate complex structures themselves
    • (Example: Creating Account, Opportunity, OpportunityLineItem, Quote, QuoteLineItem all together)
  1. Providing only minimal structures and building upon them externally
    • (Example: Creating only Account and Opportunity, then manually adding Quote and beyond in each test)

Both approaches have their pros and cons, but they share a critical flaw:

All record insertions involve the database

  • Insert parent records
  • Child records reference parent IDs and insert
  • Grandchild records also insert sequentially...

This repetition, combined with trigger execution and unnecessary processing with each data insertion, results in increasingly longer test class execution times.

As test speed deteriorates, development teams often make the worst possible choice:

"It's impossible to write sufficient tests anymore. Let's just write test classes that barely exceed the 75% deployment threshold."

This represents one manifestation of TestDataFactory's limitations. Apex classes created after this symptom appears either lack sufficient test cases or have excessively time-consuming test cases.

🎯 About Selector Pattern

Salesforce officially recommends another pattern: the "Selector Pattern."

Simply put, this is a design that extracts only the "SELECT processing" from the Repository pattern. The philosophy is to enhance reusability of retrieval logic by consolidating necessary SELECT statements per object.

Considering the TestDataFactory impact mentioned earlier, it's natural to think: "What if we mock these selector classes and replace SELECT results without accessing the database? Could this prevent test class collapse?"

However, this approach also has limitations.

⚠️ Structural Problems with Selector Pattern

While Selector Pattern aims for "object-centric reusability," what commonly happens in practice is:

  • Similar but slightly different queries keep increasing
  • Flag parameters become necessary to handle minor differences

As a result, selector classes fall into one of these situations:

  • Methods explode and become complex
  • Flag hell with unclear conditional branching

🤔 Is Mocking Selector Pattern Realistic?

When actually attempting to mock selector classes, you face additional problems.

Limitations of Interface-based Mocking

In most cases, you define something like SelectorInterface for selector classes and implement it with MockSelector. However, this design has the following issues:

  • Interface definition modification required every time a method is added
  • All implementation classes must support that method
  • Dummy implementations (empty implementations or exception throwing) needed even for unused methods

This results in deteriorated maintainability and the need to write massive amounts of "code for mocking."

Limitations of virtual + override Replacement

To avoid this, you might consider using virtual methods and overriding only necessary parts. While this appears smart, it doesn't solve the fundamental issues of selector class bloat, complexity, and context loss mentioned earlier. The root structural problems remain unresolved.

🕳️ Often Forgotten Pitfall: Are DML Operations Also Mocked?

There's another point many developers overlook: "Not only data retrieval, but DML operations (insert/update/delete) also cause execution time and side effects in tests."

  • Data insertion (insert) triggers execution
  • Data updates (update) activate validation and Process Builder
  • Data deletion (delete) fires related record cascade deletion and flows

In other words, mocking only retrieval is incomplete for test performance improvement.

🚫 Don't Blindly Trust the DRY Principle

Salesforce officially adopts Selector Pattern "to prevent duplicate code (copy-paste)," which is practicing the DRY (Don't Repeat Yourself) principle.

However, I want to strongly caution about this approach.

"Class A and Class B have the same query, so let's consolidate them."

This is a common scenario. But if A and B have different contexts, this consolidation can be very dangerous. When only A's specifications change in the future, it might unintentionally affect B.

The DRY principle originally assumes "reuse should occur within matching contexts." Ignoring this and "consolidating because they're similar" creates systems fragile to change.

Even Salesforce's official guides don't emphasize this point strongly enough, which I find problematic.

📉 Conclusion: Selector Pattern Also Lacks Sustainability

Summarizing the discussion so far, both TestDataFactory and Selector Pattern may seem convenient initially, but they reach limitations in maintainability, performance, and scalability as projects grow.

  • Factory becomes heavy with DML and trigger hell
  • Selector design breaks down due to bloat and consolidation pitfalls

💡 What Should We Do?

Solution: Query Delegation Pattern and Apex Eloquent

The answer I arrived at was a design philosophy of "separating responsibilities to make mocking easier."

Query Delegation Pattern

  • Domain layer constructs queries
  • Repository focuses on I/O like "retrieval" and "storage"

This configuration allows SELECT granularity to be designed according to "domain purposes." Even when "A-purpose" and "B-purpose" need similar queries, keeping them separate is acceptable, enabling DRY principle application according to context.

To practice this Query Delegation Pattern, after repeated implementation and verification, I arrived at Apex Eloquent.

🎭 Final Thoughts

TestDataFactory and Selector Pattern are adopted in many projects, being officially recommended by Salesforce. However, whether they're "sustainable" is a different matter.

If you're feeling limitations in your current test design or data retrieval strategy, consider adopting Query Delegation Pattern and Apex Eloquent.