1

I'm trying to deploy a SandboxRefresh script to prod. One of the main things that script does is replicate the structure of Chatter groups we use so that I can then use Processes in my sandbox that post to those groups. (I'l still have to update the Processes, but at least the groups will already exist, so one fewer manual step.)

But in trying to deploy my wonderful new SandboxRefresh script and its test class to Prod, my new test class fails with this error:

System.DmlException: Insert failed. First exception on row 0; first error: DUPLICATE_VALUE, An active or archived group with this name already exists or is being deleted. Choose a different name or try again in a few minutes.: [Name] 
Stack Trace: Class.SandboxRefreshScript.runApexClass: line 37, column 1 Class.System.Test.testSandboxPostCopyScript: line 85, column 1 Class.TestSandboxRefreshScript.testMySandboxPrep: line 9, column 1

So I'm in a catch-22: The test won't deploy because it tries to create groups with the same name as existing groups. But if I change the names of the groups in my classes, then it won't do what I actually want it to do in the sandboxes....

How can I get around this?

Sandboxrefreshscript class:

global class SandboxRefreshScript implements SandboxPostCopy {
global void runApexClass(SandboxContext context) {
    // Change value of Alias below to the Alias of user who should own the group
    // ensuring that it is unique among your users
    List<User> groupOwner = [SELECT Id, Username FROM User WHERE Alias = 'MKolo'];
    Id grpOwnerId = groupOwner[0].Id;

    // Create a Chatter Group for Payment Pipeline Changes
    List<CollaborationGroup> groups = new List<CollaborationGroup>();

    CollaborationGroup pipeline = new CollaborationGroup(
        CollaborationType = 'Public',
        Description = 'Group for showing changes to the cashflow pipeline. ',
        Name = 'Payment Pipeline Changes',
        OwnerId = grpOwnerId
    );
    groups.add(pipeline);

    //Create another Chatter Group for All Spark
    CollaborationGroup allSpark = new CollaborationGroup(
        CollaborationType = 'Public',
        Description = 'All Spark group. ',
        Name = 'All Spark',
        OwnerId = grpOwnerId
    );
    groups.add(allSpark);

    //Create another Chatter Group for Hiring Notifications
    CollaborationGroup hiringNotifications = new CollaborationGroup(
        CollaborationType = 'Private',
        Description = 'Hiring Notifications group. ',
        Name = 'Hiring Notifications',
        OwnerId = grpOwnerId
    );
    groups.add(hiringNotifications);

    insert groups;


    //Create a Task for yesterday (so it's immediately overdue) that
    //reminds to update Process Builders to use the new Chatter Groups.
    Task newTask = new Task(
        Subject = 'Fix Process Builders',
        ActivityDate = Date.today()-1,
        OwnerId = grpOwnerId
    );
    insert newTask;

    StarWarsTestDataUtil.createJediStudents();
    StarWarsTestDataUtil.createCampaignHierarchy();
    StarWarsTestDataUtil.createNextYrCampaignHierarchy();
    StarWarsTestDataUtil.createStarWarsCompaniesWithMentors();
    StarWarsTestDataUtil.createStarWarsRecruitCamp();
    StarWarsTestDataUtil.createStarWarsPrograms();
    StarWarsTestDataUtil.putMentorsOnCampaign();
    StarWarsTestDataUtil.putStudentsOnCampaign();
    StarWarsTestDataUtil.putProgramOnCatEnts();
    StarWarsTestDataUtil.putMentorships();

}

}

Test Class:

@isTest

class TestSandboxRefreshScript {

@isTest
static void testMySandboxPrep() {

    Test.startTest();

    Test.testSandboxPostCopyScript(
        new SandboxRefreshScript(), UserInfo.getOrganizationId(),
        UserInfo.getOrganizationId(), UserInfo.getOrganizationName());

    Test.stopTest();

    // Test for Payment Pipeline Changes
    List<CollaborationGroup> chGroup = [SELECT Id, CollaborationType from CollaborationGroup WHERE Name = 'Payment Pipeline Changes'];
    System.assertEquals(1, chGroup.size(), 'Chatter Group List size not equal to 1');
    System.assertEquals('Public', chGroup[0].CollaborationType, 'Chatter group is not public');

    // Test for All Spark
    List<CollaborationGroup> chGroup2 = [SELECT Id, CollaborationType from CollaborationGroup WHERE Name = 'All Spark'];
    System.assertEquals(1, chGroup2.size(), 'Chatter Group List size not equal to 1');
    System.assertEquals('Public', chGroup2[0].CollaborationType, 'Chatter group is not public');

    // Test for Hiring Notifications
    List<CollaborationGroup> chGroup3 = [SELECT Id, CollaborationType from CollaborationGroup WHERE Name = 'Hiring Notifications'];
    System.assertEquals(1, chGroup3.size(), 'Chatter Group List size not equal to 1');
    System.assertEquals('Private', chGroup3[0].CollaborationType, 'Chatter group is not private');

    //Test for Task Insert
    List<Task> tasks = [SELECT Id, ActivityDate, Subject from Task WHERE Subject = 'Fix Process Builders'];
    System.assertEquals(1, tasks.size(), 'Task Group List size not equal to 1');

    //Tests for StarWarsTestDataUtil are below
    //test createJediStudents
    list<Account> schools = [SELECT Id, School_District__c from Account WHERE RecordType.Name = 'Partner School'];
    system.assert(schools.size() == 1);
    system.assert(schools[0].School_District__c != null);
    list<Contact> students = [SELECT Id from Contact WHERE RecordType.Name = 'Spark Student'];
    system.assert(students.size() == 4);
    list<Contact> parents = [SELECT Id from Contact WHERE RecordType.Name = 'Student Family Member'];
    system.assert(parents.size() == 4);

    //test that all campaigns were created (this year hierarchy =4, next year =4, student recruit, mentor recruit)
    list<Campaign> campsNow = [SELECT Id, Name from Campaign];
    system.assert(campsNow.size() == 10);

    //test createStarWarsCompaniesWithMentors();
    list<Account> allaccts = [SELECT Id, Name FROM Account WHERE RecordType.Name = 'Corporate'];
    system.assert(allaccts.size() == 2);
    list<Contact> mentContacts = [SELECT Id FROM Contact WHERE RecordType.Name = 'Individual'];
    system.assert(mentContacts.size() == 4);

    //test createStarWarsPrograms();
    list<yat__Program__c> progs = [SELECT Id, Name from yat__Program__c WHERE Name LIKE '%Jedi Middle School%'];
    system.assert(progs.size() == 3);
    list<yat__Program__c> progswith = [SELECT Id, Name from yat__Program__c WHERE Name LIKE '%Withdrawn%'];
    system.assert(progswith.size() == 2);

    //test contacts on campaigns
    Campaign mentcamp = [SELECT Id, NumberOfContacts FROM Campaign WHERE Name LIKE '%Star Wars (test data) Mentor Recruitment Spring SY%'];
    system.assert(mentcamp.NumberOfContacts == 4);
    Campaign stucamp = [SELECT Id, NumberOfContacts FROM Campaign WHERE Name LIKE '%Star Wars (test data) Student Recruitment SY%'];
    system.assert(stucamp.NumberOfContacts == 4);


    //test putProgramOnCatEnts();
    list<Catalog_Entry__c> catEnts = [SELECT Id, Assigned_Program__c from Catalog_Entry__c];
    system.assert(catEnts[0].Assigned_Program__c != null);

    //test putMentorships();
    list<Apprenticeship__c> ments = [SELECT Id, Student_Name__c from Apprenticeship__c];
    system.assert(ments[0].Student_Name__c != null);
    system.assert(ments.size() == 4);

}

}

  • 1
    Could you share the test class, or at least a skeleton of how you're validating its behavior? I'm certain there's a dependency-injection approach, but this might be a situation where you're comfortable gating one line of code with Test.isRunningTest(). – David Reed Jan 23 at 20:37
  • Of course. I'm going to edit the original post to show it. – Michael Kolodner Jan 23 at 21:05
  • PS - I'm pretty new to coding. So I don't actually know what "you're comfortable gating one line of code with Test.isRunningTest()" means. – Michael Kolodner Jan 23 at 21:08
  • Have you tried to set @seeAlldata = false in the test script? – abhi Jan 23 at 21:11
1

I think there's a simple, pragmatic way to cure this, while still validating the behavior of your class.

Move groups to be a member variable and expose it in test context:

List<CollaborationGroup> groups;

becomes, at the class level,

@TestVisible
private List<CollaborationGroup> groups;

Gate your insert on CollaborationGroup so that it doesn't execute in test context:

    if (!Test.isRunningTest()) insert groups;

(This is not generally good unit test practice, but I think on balance it is a reasonable solution here).

Then, in your unit test, instead of writing queries against CollaborationGroup and validating the results (as one should, in general!), access and validate the groups member variable - the records that would have been inserted were we not running a unit test.

List<CollaborationGroup> chGroup = [SELECT Id, CollaborationType from CollaborationGroup WHERE Name = 'Payment Pipeline Changes'];
System.assertEquals(1, chGroup.size(), 'Chatter Group List size not equal to 1');
System.assertEquals('Public', chGroup[0].CollaborationType, 'Chatter group is not public');

becomes

Test.startTest();
SandboxRefreshScript s = new SandboxRefreshScript();
Test.testSandboxPostCopyScript(
    s, UserInfo.getOrganizationId(),
    UserInfo.getOrganizationId(), UserInfo.getOrganizationName());
Test.stopTest();

CollaborationGroup chGroup = s.groups[0];
System.assertEquals('Public', chGroup.CollaborationType, 'Chatter group is not public'); 
// other assertions

(n.b. I haven't tried this approach but I believe it'll work).

  • So, I think you're probably right that this would work and I might end up implementing. But now I want to ask the more theoretical question: Do you think I should, instead, use a proper testing method (actually inserting groups) and get around the Catch-22 by appending " - Sandbox" to their names? The original reason to do this was so that my Processes with Chatter posts would work in Sandbox (and upon deploy to Prod). But they don't anyway and have to be edited to use even a group with the exact same name. So this has me thinking I should use test code that meets best practices... – Michael Kolodner Jan 24 at 0:07
  • Hmm. It's a good question, @MichaelKolodner, and someone else might give you a different answer than I will - but I'll say, six of one/half a dozen the other. Either way you're distorting the Platonic ideal of what your code might be. Munging the name still leaves you vulnerable should someone create such a Group in Prod to unexpected test failures. I personally would just gate out the insert during a test. You could, if you wanted, dependency inject it with a delegate class to validate that the insert occurs, but for a post-copy class, that seems like overkill to me. – David Reed Jan 24 at 0:15
  • ... that said, if you're interested in going that route, I'd be happy to add a quick sketch of what it would look like. – David Reed Jan 24 at 0:16
1

I would address this with the fflib/Trailhead UnitOfWork pattern and ApexMocks

Instead of doing DML in the testmethod, you would do the following (Application class setup omitted - you'll need to read about it)

List<User> groupOwner = [SELECT Id, Username FROM User WHERE Alias = 'MKolo']; // I would personally use some value from custom metadata
Id grpOwnerId = groupOwner[0].Id;

fflib_ISobjectUnitOfWork uow = Application.UnitofWork.newInstance();
uow.registerNew(new CollaborationGroup (
        CollaborationType = 'Private',
        Description = 'Hiring Notifications group. ',
        Name = 'Hiring Notifications',
        OwnerId = grpOwnerId));

etc for all other groups
uow.commitWork();

Then, your testmethod looks like this (explanation of ApexMocks omitted - you'll need to read about it - or see Force.com Enterprise Architecture Second Edition Chapter 12)

fflib_ApexMocks mocks = new fflib_ApexMocks();
//  Given mockUow (injected)
fflib_SobjectUnitOfWork mockUow = (fflib_SobjectUnitOfWork) 
                                    mocks.mock(fflib_SObjectUnitOfWork.class);
Application.UnitOfWork.setMock(mockUow);

// When class called
Test.testSandboxPostCopyScript(
    new SandboxRefreshScript(), UserInfo.getOrganizationId(),
    UserInfo.getOrganizationId(), UserInfo.getOrganizationName());

// Then verify commitWork called
((fflib_SobjectUnitOfWork) mocks.verify(mockUow,mocks.times(1).description('commitWork sb called')))
            .commitWork();


// Then verify sobjects created as expected
   ((fflib_SobjectUnitOfWork) 
      mocks.verify(mockUow,mocks.times(1).description('group x sb created')))
                .registerNew(fflib_Match.sObjectWith( 
      new map<SObjectField,Object> {
        CollaborationGroup.Name => 'Hiring Group',
        CollaborationGroup.xxx => etc,  
                        }));

 ... repeat for other groups

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.