10

I wrote a trigger that creates some CampaignMemberStatus records against a Campaign automatically, and noticed that when I tried to test my logic any queries that I run for CampaignMemberStatus against Campaigns in a unit test seem to return empty collections unless I turn on seeAllData. Is there a reason for this that I am missing?

I'm essentially running this query and returning results (which are empty without seeAllData). I played with the runAs user being some different profiles as well.

SELECT Label, CampaignId FROM CampaignMemberStatus WHERE CampaignId =: c.Id

EDIT: Added simple unit test that fails just trying to run a query. When I execute this with seeAllData enabled it works, and when I run actual code it works fine (the two default Status values, Sent and Responded, always show up when you query for a new Campaign's CampaignMemberStatus children).

Profile p = [Select Id FROM Profile WHERE Name = 'Std Access'];

User user1 = new User(FirstName = 'Test', 
              Username = 'test1@test434.com', 
              LastName = 'User 1', 
              Email = 'test1@test.com', 
              Alias = 'test1', 
              CommunityNickname = 'test1', 
              EmailEncodingKey = 'UTF-8', 
              LanguageLocaleKey = 'en_US', 
              LocalesIdKey = 'en_US', 
              TimeZonesIdKey = 'America/Los_Angeles', 
              ProfileId = p.Id,
              UserPermissionsMarketingUser = true);
insert user1;

System.runAs(user1)
{
    Campaign c = new Campaign();
    c.Name = 'Test Campaign';
    insert c;

    List<Campaign> campaigns = new List<Campaign>();
    campaigns.add(c);

    Map<String, Set<String>> existingStatus = CampaignTriggerUtil.getExistingCampaignMemberStatusLabelsMap(new List<Campaign>(campaigns));

    System.assertEquals(1, existingStatus.size());
}

In this case, the implementation of that custom method is below (simple enough I think):

public static Map<String, Set<String>> getExistingCampaignMemberStatusLabelsMap(List<Campaign> campaigns)
{
    Map<String, Set<String>> existingStatus = new Map<String, Set<String>>();
    for (CampaignMemberStatus status : [SELECT Label, CampaignId FROM CampaignMemberStatus WHERE CampaignId =: campaigns])
    {
        Set<String> tempSet = new Set<String>();
        if (existingStatus.containsKey(status.CampaignId))
        {
            tempSet = existingStatus.get(status.CampaignId);
        }

        tempSet.add(status.Label);
        existingStatus.put(status.CampaignId, tempSet);
    }
    return existingStatus;
}
  • Was the runAs user enabled as a marketing user? – Daniel Ballinger Jul 16 '13 at 2:56
  • There is an example in CampaignMember: Status in Trigger Unit Test <> UI behaviour that shows inserting test CampaignMemberStatus records. Are you setting the SortOrder field? Maybe add your test code to the question. – Daniel Ballinger Jul 16 '13 at 3:00
  • 1
    Also, if you're not inserting the campaignmemberstatus before you query, you obviously wouldn't see the value without seealldata; that's the point of that feature. – sfdcfox Jul 16 '13 at 3:05
  • Yes, I did set the runAs user as a marketing user. I am also setting the SortOrder. I will add a simple unit test I wrote – Michael Welburn Jul 17 '13 at 3:22
12

To answer your question, yes, the CampaignMemberStatus records automatically created on insert of a Campaign are invisible to test methods annotated with @isTest(seeAllData=false).

I don't believe that you are doing anything wrong, and I have attempted to report this as a defect to salesforce a few times through H&T Cases, but have never managed to get it to be listed as a known issue.

I believe the CampaignMemberStatus records should be visible within the test class, as they are effectively created in the background by salesforce after inserting the Campaign record. If I wrote a trigger to create linked records to the Campaign they would be visible in the test class.

Here's an example of a test class to demonstrate the issue.

public with sharing class TEST_CampaignMemberStatus { 

    @isTest(seeAllData=true) 
    static void passing() { 
        Campaign c = new Campaign(Name = 'a'); 
        insert c; 

        List <CampaignMemberStatus> cmStatus = [SELECT id, CampaignID, HasResponded, IsDefault, Label, SortOrder 
                                                  FROM CampaignMemberStatus 
                                                 WHERE CampaignId = :c.Id]; 
        //expect two records 
        System.assertEquals(2,cmStatus.size()); 
    } 

    @isTest(seeAllData=false) 
    static void failing() { 
        Campaign c = new Campaign(Name = 'a'); 
        insert c; 
        List <CampaignMemberStatus> cmStatus = [SELECT id, CampaignID, HasResponded, IsDefault, Label, SortOrder 
                                                  FROM CampaignMemberStatus 
                                                 WHERE CampaignId = :c.Id]; 

        //should be 2 but is 0
        System.assertEquals(2,cmStatus.size()); 
    } 
}

On top of this, the invisible records still cause unique key violations in the seeAllData=false test method if they attempt to insert a status with the sort order 1 or 2, as the test causes the cryptic error message:

System.DmlException: Insert failed. First exception on row 0; first error: DUPLICATE_VALUE, duplicate value found: <unknown> duplicates value on record with id: <unknown>: [] 

At the moment the options to avoid this are

  1. Ensuring all tests of code that interact with the CampaignMemberStatus object run with seeAllData=true
  2. Hard coding any interactions with the CampaignMemberStatus to expect the two records that salesforce creates as standard.

Both are undesirable, for example if you have written a trigger on the Campaign object, your trigger cannot know if the CampaignMemberStatus records will be present or not when it executes. Hard coding any logic to expect the standard 2 CampaignMemberStatus records is undesirable due to the risk that if salesforce were to change the default statuses or introduce functionality for users to customise statuses then code may break.

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.