15

At this point I think we're painfully aware that using SeeAllData=true is one of the 7 Deadly Sins of Salesforce (assuming there's just 7). I know, for example, why we shouldn't use it:

  • Can slow tests down massively if you're accessing a lot of data
  • Querying for a specific record today might be gone tomorrow
  • Could cause tests to pass in sandboxes but fail in production
  • Etc...

I feel at this point I'm regurgitating the same old response to SeeAllData=true:

Using SeeAllData is almost always unnecessary except in the rarest of circumstances.

I realised though I don't actually know when it's useful to use it. So when might it be appropriate to use it? I've never come across a situation yet personally and things like Profile persist in test classes.

The only reason I think where I could be useful is that we have an object called Source__c which stores a bunch of records there the name might be Website, Mobile App, CompanyWe'reAPartnerWith1. It's basically interacted with minimally (this was made before the introduction of Custom Metadata Types), but even then I'd just do something like:

Source__c src = new Source__c(
    Name = 'Website'
);

INSERT src;

I did read that querying data is more efficient than performing DML in this way (hence @testSetup), so maybe I'm doing that "wrong"?

3 Answers 3

19

One good reason you may still need to use SeeAllData is for any functionality you have built around Report records. Your tests do not have default access to them, but they cannot be created.

static testMethod void testReports()
{
    system.assertNotEquals(0, [SELECT count() FROM Report]); // fails
}

Another instance where you may need it is Field History Tracking. If you have logic that depends on History records, I believe you cannot get them without SeeAllData.

static testMethod void testCaseHistory()
{
    Case record = (Case)SObjectFactory.create(Case.sObjectType);
    record.Status = 'Some new status';
    update record;
    system.assertNotEquals(0, [SELECT count() FROM CaseHistory]); //fails
}

Even if your code depends on these objects, you can still write it in such a way as to minimize your dependency on their presence in the database. For instance, you may write one method that just performs the query, then another that accepts the query results as a parameter. In this way, you can test most of your logic without needing to actually put data into the database.

2
  • Makes perfect sense. Never come across these scenarios as of yet!
    – Dan Jones
    Nov 17, 2016 at 14:57
  • 3
    Unit tests around the Connect API (Chatter) sometimes require SeeAllData too. Boo. Nov 18, 2016 at 2:28
5

In most cases, you can work around the SeeAllData=true limitation by using the Force.com Enterprise Apex Selector pattern and ApexMocks.

here's an example for Dashboard

Your MyClass.doSomething() method uses code such as this:

....
Dashboard[] dashboards = DashBoardsSelector.newInstance()
   .selectByDeveloperName(new Set<String> {'Foo'});

The selector follows the Enterprise pattern and looks like this (excerpted):

public virtual class DashboardsSelector implements IDashboardsSelector{


    public List<Schema.SObjectField> getSObjectFieldList(){
        return new List<Schema.SObjectField> {
                Dashboard.Description,
                Dashboard.DeveloperName,
                ...
        };
    }

    /*  Factory to provide caller with a new Selector. Enables selectors to be mocked */ 
    public static IDashboardsSelector newInstance()    {
        return (IDashboardsSelector)Application.Selector.newInstance(Dashboard.SObjectType);
    }

    /* getSObjectType : Used to construct queries. Required. */
    public Schema.SObjectType getSObjectType() {return Dashboard.SObjectType;}

    /* selectById   : default selector, returns all matching SObjects for fields defined by getSObjectFieldList */
    public virtual List<Dashboard> selectById(Set<Id> ids) {
        return ids.isEmpty() ? new List<Dashboard> () : (Dashboard[]) selectSObjectsById(ids);
    }

    /* selectByDeveloperName  : fetches dashboard for a devname set */
    public virtual List<Dashboard> selectByDeveloperName(Set<String> devNames) {
        if (devNames.isEmpty()) return new List<Dashboard> ();
        fflib_QueryFactory dbQF = newQueryFactory()
                .setCondition('DeveloperName IN :devNames');
        return Database.query(dbQF.toSOQL());
    }
}

The interface for factory and mocking is:

public interface IDashboardsSelector {
  List<Dashboard> selectByDeveloperName(Set<String> devNames);
}

and your Application class has an entry for the selector (not shown here)

Now, your testmethods don't need to use SeeAllData=true, they merely need to mock the selector and the selector's method's return:

fflib_ApexMocks mocks = new fflib_ApexMocks();

//  Given mockDashboards (using Json serialize/deserialize technique - here with Sobject Fabricator github lib)
Dashboard[] mockDashboards = new List<Dashboard> ();
mockDashboards.add((Dashboard) new sfab_FabricatedSObject(Dashboard.class)
                .setField(Dashboard.Id,fflib_IdGenerator.generate(Dashboard.SObjectType))
                .setField(Dashboard.DeveloperName,'FooDash')
                .toSObject());

//  Given a mockSelector
DashboardsSelector mockDashboardsSelector = (DashboardsSelector) mocks.mock(DashboardsSelector.class);
mocks.startStubbing();
mocks.when(mockDashboardsSelector.SObjectType()).thenReturn(Dashboard.SObjectType);
for (Integer i = 0; i < mockDashboards.size(); i++) {
    mocks.when(mockDashboardsSelector
     .selectByDeveloperName(new Set<String> {mockDashboards[i].DeveloperName}))
        .thenReturn(new List<Dashboard>{mockDashBoards[i]});
}
mocks.stopStubbing();
Application.Selector.setMock(mockDashboardsSelector);

// when codeUnderTest method invoked
new MyClass.doSomething(...);

// then verify whatever results you care about

Now this pattern is super useful and allows you to create mock results from SOQl queries against SObjects you can't otherwise insert in testmethods. The ApexMocks library gives you flexibility with selector, domain, service, and unit of work layers and can avoid creating DML actual sobjects in most of your unit tests.

But what about testing the actual selector class itself? How do you know that the method selectByDeveloperName actually does the right thing?

Here, seeAllData=true is required but you have localized it to just the selector class itself and the rest of your unit tests need never depend on org data

private class DashboardsSelectorTest {
    @IsTest(SeeAllData=true)
    static void testSelectors() {

        Dashboard[] orgDashboards = [SELECT ID, DeveloperName From Dashboard Limit 1];
        if (orgDashboards.isEmpty()) {return;}  // can't test, no org dashboards

        Dashboard[] dbs = DashboardsSelector.newInstance()
                .selectByDeveloperName(new Set<String> {orgDashboards[0].DeveloperName});
        System.assertEquals(1,dbs.size(),'should find the real org Dashboard');

    }
}
1

While I was working on ConnectApi, I encountered an error while writing test class

System.UnsupportedOperationException: ConnectApi methods are not supported in data siloed tests. Please use @IsTest(SeeAllData=true).

So, as of now, when using ConnectAPI one would need to use SeeAllData=true.

1

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .