3

A fresher question.

As per the Apex guide online,

https://www.salesforce.com/us/developer/docs/apexcode/index_Left.htm#CSHID=apex_testing.htm|StartTopic=Content%2Fapex_testing.htm|SkinName=webhelp

the following is a test factory class

@isTest
                    public class TestDataFactory {
    public static void createTestRecords(Integer numAccts, Integer numContactsPerAcct) {
        List<Account> accts = new List<Account>();

        for(Integer i=0;i<numAccts;i++) {
            Account a = new Account(Name='TestAccount' + i);
            accts.add(a);
        }
        insert accts;

        for (Integer j=0;j<numAccts;j++) {
            Account acct = accts[j];
            List<Contact> cons = new List<Contact>();
            // For each account just inserted, add contacts            for (Integer k=numContactsPerAcct*j;k<numContactsPerAcct*(j+1);k++) {
                cons.add(new Contact(firstname='Test'+k,lastname='Test'+k,AccountId=acct.Id));
            }
            insert cons;
        }
    }
}

This test factory class from above has one sole purpose of creating generic test data and can be called from other test classes like below.

@isTest
                    private class MyTestClass {
    static testmethod void test1() {
        TestDataFactory.createTestRecords(5,3); ///QUESTION
        // Run some tests
    }
}

My question is at "QUESTION" indicated line above. The "TestDataFactory.createTestRecords()" does not return any data as indicated by "void" in its method definition. Then how "MyTestClass.test1()" will be able to obtain and test the data generated by the utility class ?

Can someone throw light on this ?

5 Answers 5

5

The createTestRecords method commits data to the database using insert.

The records that have been inserted can be retrieved using SOQL:

List<Account> accounts = [SELECT Name FROM Account]; // Will bring back the created accounts
List<Contact> contacts = [SELECT FirstName, LastName FROM Contact]; // Will bring back the created contacts

Personally I prefer to return the records I create in these types of methods as it means I have access to the inserted records (and their Id) without needing the query the database:

public static List<Account> createTestAccounts(Integer numAccts) 
{
    List<Account> accts = new List<Account>();

    for(Integer i = 0; i < numAccts; i++) 
    {
        Account a = new Account(Name = 'TestAccount' + i);
        accts.add(a);
    }

    insert accts;
    return accts;
}
1
  • I consider the former one as the better option since we have access to the inserted records no matter we are returning that inserted records list from the called method or not. In the later case we can return only one thing either list of account records or contact records. or else we can return List<List<sObject>> which I think is very cumbersome so better to stick with former way. Commented Jan 21, 2015 at 9:32
4

In general, test methods only see data that is inserted as part of the test method running. So your test could rely on only the number of Accounts and Contacts passed in to the test method to be present and base its assertions on those numbers.

But it is often useful to be able to reference the inserted data and when multiple objects are involved it is cleaner to use a class to hold those references rather than to try to return them from a method (or have to requery them). This is how that code would look:

@isTest
private class MyTest {
    class Fixture {
        Account[] accounts = new Account[] {};
        Contact[] contacts = new Contact[] {};
        Fixture(Integer numAccts, Integer numContactsPerAcct) {
            for(Integer i = 0; i < numAccts; i++) {
                accounts.add(new Account(Name = 'TestAccount' + i));
            }
            insert accounts;
            for (Integer i = 0; i < numAccts; i++) {
                for (Integer j = numContactsPerAcct * i; j < numContactsPerAcct * (i + 1); j++) {
                    contacts.add(new Contact(
                        FirstName = 'Test' + j,
                        LastName = 'Test' + j,
                        AccountId = accounts[i].Id
                        ));
                }
            }
            insert contacts;
        }
    }
    @isTest
    static void test() {
        Fixture f = new Fixture();
        // Use f.accounts to reference the Accounts as needed
        // Use f.contacts to reference the Contacts as needed
    }
}
0

When testing, you should not use DML statements. Lets say you have a test method that needs to insert some default data before doing a call-out. "Data that should normally already be there, and not inserted in that session". As we all know you can't perform DML statements prior to call-outs in the same session (unless it's an asynchronous method). APEX test methods are a bit special. They are assuming and alot and doing alot automatically. For instance, in any other language/platform, executing database statements without a DB connection wouldn't automatically create a test database, and you wouldn't want it to either. The methods for CRUD actions should be "unaware" of the system holding the data. One should have a "Store <> Storage" concept with factories. This way you wouldn't have to do DML statements in your tests. In other languages/platforms you would NEVER do any database calls in test methods, since tests should never be dependent on "existing" data. Now, I know APEX/Force.com is a special case since it creates your own test scope. But it isn't evolved enough to rely on. When you want to test call-outs combined with DML statements and asynchronous operations everything becomes very complicated. But! If you write your code in the "right way", as in writing store factories taking storage classes as parameters you would never have to bother thinking about this. I'm sorry, normalizing my thougts into text areas have never been one of my strongest capabilities but in short terms; "The way you write your code is the issue" :D

/Björn @ DevCore

2
  • can you please format your answer for easy reading. split into multiple lines / paragraphs, use boild / italics to highlight the key parts Commented Sep 18, 2014 at 23:39
  • Referring to your this line - "When testing, you should not use DML statements". In this case how we will check code coverage for a trigger that works for before or after update in salesforce. Please explain with some sample code . Thanks in Advance ! Commented Jan 21, 2015 at 11:08
0
public abstract with sharing class AccountStoreBase {

    private final AccountStorageBase storage;
    public AccountStoreBase () { storage = new AccountStorage(); }
    public AccountStoreBase (AccountStorageBase storage) { 
        this.storage = storage;
    }
    public virtual Account[] getAllAccounts() {
        storage.getAllAccounts();
    }

}

public with sharing Class AccountStore extends AccountStoreBase { 

    public AccountStore { super(); }
    public AccountStore (AccountStorageBase storage) { super(storage); }

}

public with sharing Class AccountTestStore extends AccountStoreBase {

    public AccountStore { super(); }
    public AccountStore (AccountStorageBase storage) { super(storage); }

}

public abstract with sharing class AccountStorageBase {

    private final List<SObject> sObjects;
    public AccountStorageBase() { }
    public AccountStorageBase(List<SObject> sObjects) {
        this.sObjects = sObjects;
    }
    public virtual Account[] getAllAccounts() {
        // ToDo: Insert way of specifying fields (ex: query builder)
        return [SELECT Account.Id, Contact.Email...
    }

}

public with sharing class AccountStorage extends AccountStorageBase {

    public AccountStorage() { super(); }
    public AccountStorageBase(List<SObject> sObjects) { super(sObjects); }

}

public with sharing class AccountTestStorage extends AccountStorageBase {

    public AccountStorage() { super(); }
    public AccountStorageBase(List<SObject> sObjects) { super(sObjects); }
    public override Account[] getAllAccounts() {
        List<Account> accounts = new List<Account>();
        // Since no Linq like stuff exists in Apex
        for (SObject so : sObjects) {
            if (so instanceOf Account)
                accounts.add(so.Id, (Account)so);
        }
        return accounts;
    }

}

@isTest
private class MyTest {

    @isTest
    private static void test() {
        // Note: One have to create a custom junction feature in test storage
        // if any hierarchy get methods needs to be implemented
        List<SObject> testObjects = new List<SObject>();
        List<Account> accounts = new List<Account>();
        for (Integer i = 0; i < numAccts; i++) {
            Account a = new Account(Name = 'TestAccount' + i, CustomId__c = IdGenerator.create(Account.class));
                    accounts.add(a);
            testSObjects.add(a);
        }
        Integer i= -1;
        List<Contact> contacts = new List<Contact>();
        for (Account a : accounts)
            i++;
                    for (Integer j = numContactsPerAcct * i; j < numContactsPerAcct * (i + 1); j++) {
                            Contact c = new Contact(
                                FirstName = 'Test' + j,
                                LastName = 'Test' + j,
                                AccountCustomId__c = a.CustomId__c,
                    CustomId__c = IdGenerator.create(Contact.class)
                            ));
                contacts.add(c);
                testSObjects.add(c);    
                    }
                }
        AccountStorageBase accountStorage = new AccountTestStorage(testSObjects);
        AccountStoreBase accountStore = new AccountTestStore(accountStorage);
        accounts = accountStore.getAllAccounts();
        // ToDo: Some tests
    }

}
3
  • if possible please add some details on how this code answers the question. Commented Sep 18, 2014 at 23:09
  • Your code has many syntax errors. How you are writing constructor for abstract class? Commented Jan 21, 2015 at 6:54
  • Please provide code that has relevance with the explanation you gave as I can not understand how this code is explaining the concepts you stated in your above explanation. Thanks in advance! Commented Jan 21, 2015 at 7:00
0

Please check this new test data factory library Apex Test Kit: https://github.com/ApexTestKit/ApexTestKit

@isTest
static void testAccountCreation() {
    // create 10 accounts, each has 2 contacts
    ATKWizard.Bag bag = ATKWizard.I().wantMany('Account')
        .total(10)
        .hasMany('Contact')
            .total(20)
            .fields(new Map<String, Object> {
                'FirstName' => '{!name.firstName(female)}',
                'LastName' => '{!name.lastName}'
            })
        .generate();

    List<Account> accountList = (List<Account>)bag.get('Account');
    System.assertEquals(10, accountList.size());
}

With ApexTestKit 1.0, it focuses on data generation only. If it is getting popular and wanted by the community, 2.0 will provide BDD functions.

You must log in to answer this question.

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