+ Start a Discussion
sparkysparky 

Delete list custom settings in unit test?

Is it possible to delete list custom settings records in code?  I'm getting an "invalid cross reference id" when I attempt to do this as part of a test.

 

Reason I'm doing this: the code I'm working on will be packaged and distributed to various clients.  It uses a list custom settings object to enable each client to use their own parameters.  But for the tests, I need to know what the parameters are so I know what to test for.  Since I can't know what the client will have put in for their custom settings, safest bet in the test is to delete whatever settings exist, and then replace them with  standard ones.

 

It's letting me insert new settings, but not delete existing ones.

mtbclimbermtbclimber
Thanks Matthew. This sounds like a bug. We have an engineer investigating it now.
sparkysparky

Any news on this, Andrew?

 

It also seems to be preventing me from Updating existing list settings, which was my workaround until you guys got the deletion fixed.  If I can't do either, than I think I have no reliable way to test something which relies on list settings.

 

Thanks!

M.

tmatthiesentmatthiesen

Can you provide a repro?  The below logic works:



static testmethod void testcs(){

listtest__c a = listtest__c.getInstance('one');
delete a;
system.assert (listtest__c.getInstance('one') == null);
}
sparkysparky

Hmm, you're right.  And after further examination of my code, I now suspect something more complex is going on, perhaps having to do with how settings data persists from one unit test to another.

 

I have 3 testmethods set up in my class.  Each of them begins by calling a static method that attempts to standardize what's in the custom settings list.

 

The method that gets called looks something like this:

 

 

static void setDefaultSettings() { // for tests // first delete any existing settings so there's no conflict ONEN_CampaignMbrStatusSettings__c[] oldsettings = ONEN_CampaignMbrStatusSettings__c.getAll().values(); ONEN_CampaignMbrStatusSettings__c[] setsDel = new ONEN_CampaignMbrStatusSettings__c[0]; for (ONEN_CampaignMbrStatusSettings__c setting : oldsettings) { setsDel.add(setting); } // snip - code here to insert replacement settings if (setsDel.size()>0) delete setsDel; }

 

 And here's what's happening, based on the debug log.

 

* testmethod #1 runs, and succeeds.  (Well, it fails later on in the test, but no errs are thrown by the above method  - the deletion of settings succeeds.)

* testmethod #2 fails to delete the settings, gives me the invalid cross-ref err

* testmethod #3 succeeds as #1 did

 

Furthermore, I can see via debug statements that tests #1 and #3 are deleting the "real" settings that are actually in there before I run the tests.  But test #2 is attempting to delete the settings that were inserted by test #1!  Somehow, the settings that test #1 inserted have persisted through to test #2.  But the deletion fails, so they apparently haven't really persisted.  Is the bug with the getall() method returning data from the previous test that should have been cleaned up?

 

I'm used to each testmethod being a clean slate that starts with the same database state, and that nothing is supposed to persist from one test to another.

 

After test #2 bombs, test #3 seems to reset to the starting state, and so allows the deletion again.

 

If I comment out the #2 test in one run, then a different test becomes test #2 in the next run, and it then fails in the same way.  So it has nothing to do with the testmethods themselves, it seems to be simply about which one ends up running second.

 

Hope that made sense.  If anyone from SF wants to troubleshoot this further, I'm happy to provide my full code and login access.

 

 

 

 

sparkysparky

Bump.

 

Hey Andrew, or anyone else at SF, any insight into this?  Should I just submit a case?

sparkysparky

I submitted a case.  It's # 0315583, in case any of you SF'ers have anything to contribute to it.

 

 

rungerrunger

I'm trying to reproduce this with the information you've given here.  I've got a test class that passes:

 

public class CSTest {
    static void setDefaultSettings() {
        mylist__c[] oldsettings = mylist__c.getAll().values();
        mylist__c[] setsDel = new mylist__c[0];
        
        for (mylist__c setting : oldsettings) {
            setsDel.add(setting);
        }
        
        if (setsDel.size()>0) delete setsDel;
    }
    
    @IsTest public static void testOne() {
        setDefaultSettings();
        mylist__c x = new mylist__c(name='test one');
        insert x;
    }
    
    @IsTest public static void testTwo() {
        setDefaultSettings();
        mylist__c x = new mylist__c(name='test two');
        insert x;
    }
    
    @IsTest public static void testThree() {
        setDefaultSettings();
        mylist__c x = new mylist__c(name='test three');
        insert x;
    }
    
}
Is there something that jumps out at you that I need to add to this to make your problem manifest?
sparkysparky

Thanks for looking into this, runger, whoever you are.

 

Nothing jumped out at me, so I had to do a little bit of additional testing.  Here's what I found out.  When I created a simplified class very similar to yours, I got no errors.  Only real difference is I moved the creation of settings into the separate method, rather than in each testmethod.

 

My passing code looked like this:

 

public class zzTempTestListSettings {

static void setDefaultSettings() {
// first delete any existing settings so there's no conflict
ONEN_CampaignMbrStatusSettings__c[] oldsettings = ONEN_CampaignMbrStatusSettings__c.getAll().values();
ONEN_CampaignMbrStatusSettings__c[] setsDel = new ONEN_CampaignMbrStatusSettings__c[0];

for (ONEN_CampaignMbrStatusSettings__c setting : oldsettings) {
setsDel.add(setting);
}

if (setsDel.size()>0) delete setsDel;

ONEN_CampaignMbrStatusSettings__c set1 = new ONEN_CampaignMbrStatusSettings__c (
Name = 'foo1',
Campaign_Type__c = 'Communication',
Campaign_Subtype__c = 'Email',
Status_Label__c = 'Opened',
IsResponded__c = True,
Default_Status__c = false
);
insert set1;
}

@IsTest public static void testOne() {
setDefaultSettings();
}

@IsTest public static void testTwo() {
setDefaultSettings();
}

@IsTest public static void testThree() {
setDefaultSettings();
}


}

 

but when I added a bit of the action from my original test back in, it started to fail in the same way it had to begin with.

 

My failing code looks like:

 

 

public class zzTempTestListSettings {

static void setDefaultSettings() {
// first delete any existing settings so there's no conflict
ONEN_CampaignMbrStatusSettings__c[] oldsettings = ONEN_CampaignMbrStatusSettings__c.getAll().values();
ONEN_CampaignMbrStatusSettings__c[] setsDel = new ONEN_CampaignMbrStatusSettings__c[0];

for (ONEN_CampaignMbrStatusSettings__c setting : oldsettings) {
setsDel.add(setting);
}

if (setsDel.size()>0) delete setsDel;

ONEN_CampaignMbrStatusSettings__c set1 = new ONEN_CampaignMbrStatusSettings__c (
Name = 'foo1',
Campaign_Type__c = 'Communication',
Campaign_Subtype__c = 'Email',
Status_Label__c = 'Opened',
IsResponded__c = True,
Default_Status__c = false
);
insert set1;
}

@IsTest public static void testOne() {
setDefaultSettings();

//added MMS

//create a campaign that WON'T trip the trigger to ensure that all of the statuses
//exist in the CampaignMemberStatus table -- to prevent test errors when statuses don't
//already exist in production.
Campaign newCampaign = new Campaign (
Name = 'Bogus_Campaign'
);
insert newCampaign;

List<CampaignMemberStatus> newStatuses = new List<CampaignMemberStatus>();
CampaignMemberStatus statusSix = new CampaignMemberStatus(
Label = 'Invited',
CampaignId = newCampaign.id,
HasResponded = false,
SortOrder = 12
);
newStatuses.add(statusSix);

CampaignMemberStatus statusSeven = new CampaignMemberStatus(
Label = 'Attended',
CampaignId = newCampaign.id,
HasResponded = true,
SortOrder = 13
);
newStatuses.add(statusSeven);

if ( newStatuses.size() > 0 ) {
insert newStatuses;
}

}

@IsTest public static void testTwo() {
setDefaultSettings();
}

@IsTest public static void testThree() {
setDefaultSettings();
}


}

 

When I run this, testOne (which runs first) passes.  testThree (which runs next) fails with "invalid cross reference id" on the custom setting deletion.    testTwo (which runs last) also fails w/ invalid cross reference id.  This is different from my original code, in which the third test to run doesn't encounter this issue.

 

So I have no idea why it behaves this way.  It still seems totally buggy to me.  But maybe it's because I'm doing some other DML operations after doing the setting DML's?  Or because I'm causing other triggers/classes to run?  BTW, none of that other code writes anything to the custom settings object - it only reads from it.

 

 

 

 

sparkysparky

As of today, I'm getting a completely different error message for this same code.  Haven't changed the code at all.  So SF must have pushed a change in the last week or so?

 

Now, I'm getting:  "MIXED_DML_OPERATION, DML operation on setup object is not permitted after you have updated a non-setup object (or vice versa)"

 

I don't quite understand this, especially the vice versa part.  Does that mean that any transaction that DML's any regular sobject record cannot also DML custom settings, no matter which order it is done in?  If that's the case, how are we expected to write tests for code that relies on settings?  The tests have to pass during deploy, at which time of course the custom settings object will be empty.

 

Help, please!

 

BTW, I have gotten nowhere fast with support.  First support guy got in touch, then promptly went on vacation.  The guy he passed me off to has not answered any of my emails.

rungerrunger

Sorry, I've been on vacation as well, and I'm the engineer who's been looking at this.  The MIXED_DML issue is known, and fixed in the next release (Winter '10).

 

For the non-test case, it's possible to put the settings change in a @Future method to get around this, but when testing, this will just be inlined, so you'd see the same error.

 

Sorry for the delay.

 

Rich Unger

sparkysparky

Hey Rich, thanks for the reply, and hope your vacation was good.  Just so I'm clear, can you say again how exactly this will work after the release of Spring '10?

 

Will DML's to custom settings and regular objects always be allowed within a single unit test?  Will they also be allowed in regular code without using @future, or only in tests?

 

Tests are what I'm most concerned about, but would just like to know what the plan is.

 

Thanks!

 

rungerrunger

Oops, yes, I meant Spring '10 :)

When Spring '10 is released, you will be able to do DML to custom settings and regular objects in the same block of code, without the @Future hack.  This will work in and out of tests.

 

Basically, during the pilot phase we classified custom settings as setup objects (like Profile, etc), but they no longer need to be that way.

 

Rich Unger 

sparkysparky
OK, i guess the only remaining part of the mystery then is why I'm not getting this error when I run the tests in Eclipse, only when I run them in the SF UI.  Is it b/c there wasn't a Winter 10 version of the IDE released, and it's using an older version of the unit test API?
Ron WildRon Wild

Rich,

 

Are you sure about that?  Spring '10 is out and I'm still getting:

 

MIXED_DML_OPERATION, DML operation on setup object is not permitted after you have updated a non-setup object (or vice versa):

 

.. when I try to update a custom setting in an on update trigger.

 

- Ron

tmatthiesentmatthiesen
Can you post your test class?
Message Edited by tmatthiesen on 03-15-2010 07:36 PM
Ron WildRon Wild

Sure...

 

 

trigger UpdateKeychain on pymt__Processor_Connection__c (before insert, before update) { String mask = '************'; // Make sure settings object is available Keychain__c[] keychain = Keychain__c.getall().values(); Keychain__c[] keysToUpdate = new Keychain__c[]{}; User thisUser = [ select Id from User where Id = :UserInfo.getUserId() ]; Keychain__c key; for (pymt__Processor_Connection__c pc :trigger.new) { key = null; for (Keychain__c item :keychain) { if (!Util.isNullOrEmpty(item.Processor_Connection__c) && item.Processor_Connection__c == pc.id) { key = item; break; } } if (key == null) { key = new Keychain__c(Processor_Connection__c = pc.Id); } if (key <> null) { if (pc.PP_API_Username__c <> '' && !masked(pc.PP_API_Username__c)) { key.Username__c = pc.PP_API_Username__c; //pc.PP_API_Username__c = mask; } if (pc.PP_API_Password__c <> '' && !masked(pc.PP_API_Password__c)) { key.Password__c = pc.PP_API_Password__c; pc.PP_API_Password__c = mask; } if (pc.PP_API_Signature__c <> '' && !masked(pc.PP_API_Signature__c)) { key.Key__c = pc.PP_API_Signature__c; pc.PP_API_Signature__c = mask; } keysToUpdate.add(key); } } if (keysToUpdate.size()>0) { upsert keysToUpdate; // causes: MIXED_DML_OPERATION, DML operation on setup object is not permitted after you have updated a non-setup object (or vice versa): } public Boolean masked(String val) { if (val == null || val == '') return false; if (val.length() > 6 && val.contains('******')) return true; return false; } }

 

 

 

rungerrunger
The fix is versioned.  Your code has to be set to the latest version for custom settings not to be considered "setup".
sparkysparky

Hi runger,

 

We're still getting this error, on a test class that is latest version (18).  Like before, it's only when run in the SF UI, not in Eclipse.

 

Please advise.

 

rungerrunger

Are you talking about the ONEN_CampaignMemberStatus class?  (That's the one mentioned in your support case)

It looks to be version 17 to me...

sparkysparky
No, this is a new class in a different instance.  I can grant you login access if you want..
rungerrunger
Yes, please.  Let me know the org id, and which class you're talking about.
sparkysparky
PM sent.  Thanks for looking into this!
sparkysparky

Just wanted to let all the others on this thread know that I sent a PM to runger a few weeks ago with login access and more details about this issue, but haven't heard a peep from him.  So seems that this is still an issue (not solved) and no word as to expected resolution.

nelloCnelloC

I don't know if this is related but I've been having some problems with an update to a custom setting when called from a test method: http://community.salesforce.com/t5/Apex-Code-Development/Invalid-cross-reference-id-on-update-of-custom-setting/td-p/185030

 

 

 

rungerrunger

You didn't get my response?  Sorry, I just saw this message, but I responded to your PM on 3/26.  The contents of the message were:

 

Your tests end up causing triggers to be run.  One of those triggers does DML on the custom setting, and that trigger's version is 14.  It's ONEN_Opportunity_CreateMirrorPayment.  If you change that trigger's version to 18, you won't get MIXED_DML problems.

 

I don't see how you could have not gotten it.

sparkysparky

Hey runger,

 

I don't know what happened either!  When I look at my inbox now, I see your PM from 3/26, but I swear to you that last month it wasn't there.  Anyway, thanks for getting back to me.

 

But I don't really understand your diagnosis here.  The trigger you mention, ONEN_Opportunity_CreateMirrorPayment, does not do any DML on a settings object.  In fact, that trigger was written long before custom settings were available, and doesn't use custom settings at all.  I just double checked, and the only DML it does is on the custom object OppPayment__c, which is not a settings obj. 

 

I can grant you login access again, if that would help.

 

Thanks for looking into this!

rungerrunger

Hmm, that's strange.  I ran your tests in a copy of the org with that trigger's version updated (back in march), and it worked.

 

Anyway, you have several classes that reference/update settings that have an older version:

ONEN_TEST_CampaignSetMemberStatuses

ONEN_CTRL_CloneMonthlyGifts

ONEN_BATCH_CloneMonthlyGifts

ONEN_CampaignMemberStatus

 

At any rate, I am sure that there's a versioning issue here.  The MIXED_DML check only happens in relation to older versions.

sparkysparky

Hello runger, I'm coming back to this after being tied up with other things for a while.

 

So could the issue be that although the test class doing the DML on the custom settings obj is v18, it also does other DMLs on standard objects, wihch in turn fires some triggers, which in turn runs code in classes that are older older versions?   Does every piece of code triggered as part of the transaction have to be v18?

 

If not, then my results are not the same as yours.

 

Thanks much!

rungerrunger

Matthew-

Sorry, I'm going to be on paternty leave for a while.  I won't get a chance to look at this.  My sleep-deprived brain is playing catchup!

 

Rich

rungerrunger

That said, the rule is: if setup DML and non-setup DML occur in the same transaction, the error will be thrown.  CS objects are considered setup if the DML for that object occurs in the < 18.0 code unit (trigger or class).

Chirag MehtaChirag Mehta

I'm using version 21.0, still I get the same error. 

 

I've a trigger on user, which inserts welcome chatter post in future. Test methods is giving mixed dml error, when executed from UI. However, from eclipse there's no error.

 

It's Strange!