0

I an trying to create a testdatafactory which i can use to call in my tesclasses. What I want to achieve is small buildingblocks with which I call another method if a lookupfield needs to be filled. I tried this with account and opportunities which works great if I only call the opportunity from the test class but if I call both I get error: DUPLICATE_VALUE, duplicate value found: duplicates value on record with id: : []

Here is my example

@isTest
public class TestDataFactory {

    public static List<Account> createAccounts(Integer numAccts) {
        //List<Country__c> CountryList = createCountries(1);
        Country__c c = new Country__c(Name  = 'Nederland' , ISO2Code__c  = 'NL' , IBANLength__c  = 18, ISO3Code__c = 'NLD' , CountryNumber__c =151);
        insert c;
        List<Account> accList = new List<Account>();
        for(Integer i=0;i<numAccts;i++) {
            Account a = new Account(Name='TestAccount' +'-'+ i , Country__c = c.id);
            accList.add(a);
        }
        insert accList;
        return accList;
    }

    public static List<Opportunity> createOpportunities(Integer numOpp) {
        List<Account> accList = createAccounts(numOpp);

        List<Opportunity> oppList = new List<Opportunity>();
        for(Integer i=0;i<numOpp;i++) {
            Opportunity opp = new Opportunity(Name='TestOpportunity' +'-'+ i , Account = accList[0], StageName = 'Closed Won', CloseDate = system.today()+30);
            oppList.add(opp);
        }
        insert oppList;
        return oppList;
    }
}

Works

@isTest
public class test {
    @isTest static void tester(){
    system.debug('createOpps ' + TestDataFactory.createOpportunities(5));
}
}

ERROR

 @isTest
    public class test {
        @isTest static void tester(){
        system.debug('createAccounts ' + TestDataFactory.createAccounts(5));
        system.debug('createOpps ' + TestDataFactory.createOpportunities(5));
    }
    }
3
  • Your createOpportunities method also calls createAccount, so it's called twice. Both of the times, the same Country gets created, I'm guessing there's a unique field on that object. Sep 27, 2018 at 14:24
  • @rael_kid This is the case. But still if I wantend to test a map with account and opportunities like public static map<Account,Opportunity> createMap(acc, opp), how can I make use of the methods?
    – Thomas
    Sep 27, 2018 at 14:31
  • You could create Accounts first and pass them to the Opportunity-function. You could also check if the country exists, and if so, don't create it again. Or you could create a separate function to create the Country and make sure that is called only once. Usually I have my 'factory' functions return the record(s) that they create, so I can use them later. (I just noticed you have that too, but you don't use the return values) Sep 27, 2018 at 14:33

2 Answers 2

3

I think that this error is stemming from the fact that your data building methods do more work than the method names suggest.

your createAccounts() method also creates a Country__c record
your createOpportunities() method also calls createAccounts()

I'm not sure if the error is happening because you're trying to insert another Country__c record when you call createAccounts() followed by calling createOpportunities(), or if you're running into that error when inserting the second set of accounts (which will create Test Account - 0, Test Account - 1, etc... a second time), but clearly something is being detected as a duplicate.

One approach that could solve this issue would be to memoize the results of calling each method, so that if you end up calling the same creation method again, you'll return the records that were created the first time.

An example implementation

public class TestDataMaker{
    // Store a list of accounts that we've previously created.
    // This enables us to save ourselves some work, not creating Accounts we've
    //   already created
    private List<Account> createdAccounts = new List<Account>();

    public List<Account> createAccounts(Integer num){
        List<Account> results = new List<Account>();

        for(Integer i = 0; i < num; i++){
            Account theAccount;

            // If we've already created an account, then just pull the account
            //   that we've already created.
            if(this.createdAccounts.size() > i){
                theAccount = this.createdAccounts[i];
            }else{
                theAccount = new Account(...);

                // This is the other important part of the memoization
                // When we _do_ create a new account, we need to add it to our
                // memo-ized list of accounts
                this.createdAccounts.add(theAccount);
            }

            results.add(theAccount);
        }

        return results;
    }
}

I think a better approach would be to limit your creation methods to do "what it says on the tin". I.e. createOpportunities() should only create Opportunity records. If you need Accounts to relate to the Opportuniities, then take that as an argument to createOpportunities()

0

My advice would be to have the a list of accounts be an input variable into your createOpportunity method instead of calling the createAccounts method from within.

So change your createOpportunity method to:

public static List<Opportunity> createOpportunities(List<Account> acctList, Integer numOpp) {

    List<Opportunity> oppList = new List<Opportunity>();
    for(Integer i=0;i<numOpp;i++) {
        Opportunity opp = new Opportunity(Name='TestOpportunity' +'-'+ i , Account = accList[0], StageName = 'Closed Won', CloseDate = system.today()+30);
        oppList.add(opp);
    }
    insert oppList;
    return oppList;
}

Example

List<Account> acctList = createAccounts(5);
List<Opportunity> oppList = createOpportunity(acctList, 5);

You must log in to answer this question.

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