6

So, I am writing some Apex Unit tests, and I don't want to go through the entire flow of setting up test data for every test case. Right now I am using SeeAllData=true, but I was wondering if there was a way to set up test fixtures that I could use for all of my tests.

8

Many people set up test data creation classes. Lots of time you will see them referred to as factories. So you could set up a TestFactory like below

public with sharing class TestDataFactory {

           public static Account accountCreator(string name){
                   Account myAccount = new Account();
                   myAccount.Name = name;
                   insert myAccount;
                   return myAccount;
           }

           public static Contact contactCreator(Id accountID, string fname, string lname){
                   Contact myContact = new Contact();
                   myContact.firstName = fname;
                   myContact.lastName = lname;
                   myContact.AccountId = accountID;
                   insert myContact;
                   return myContact;
          }
    }

Now your test method that used to look like this

static testMethod void myTestMethod() {

     Account a = new Account();
     a.Name = 'test';
     insert a;

     Contact c = new Contact();
     c.FirstName = 'Joe';
     c.LastName = 'Schmoe';
     c.AccountId = a.Id;
     Insert c;
     //Rest of your code
}

Now looks like this

static testMethod void myTestMethod() {

     Account a = TestDataFactory.accountCreator('Test Account');
     Contact con = TestDataFactory.contactCreator(a.Id, 'Joe', 'Schmoe');
     //Rest of your code
}

8 Lines becomes 2. You can imagine how much time and code this can save you in complex test classes. Your code is much cleaner and its totally reusable. This is just a very simple example, you can do quite a bit with this and expand on it to save you lots of time in creating your test data. Hope this helps

| improve this answer | |
  • 2
    Jesse Altman had a good point. Be sure to add the @isTest annotation to the TestDataFactory class – edgartheunready Jan 30 '14 at 18:49
  • I always do, I just didn't put full classes here. This was more just some short hand code I wrote for demo purposes. His point is valid though, you should add @isTest to the factory/Utility Classes – Chris Duncombe Jan 30 '14 at 18:52
  • For the other rookies out there, you add the @isTest annotation at the top of the class so that this class itself doesn't need to be unit tested and doesn't count against the 75% code coverage that SF requires. – Joshua Dance Oct 2 '14 at 15:41
7

A month or two ago I wrote an article on proper unit test structure you may find useful. One of the most important things about unit tests in Salesforce is that they are actually integration tests. This requires you to generate your entire data model every time you write a test. They don't have a mock framework in place.

With that said, and building off of sfdc_ninja's suggestion of a factory class (although I call them Utility classes - it doesn't really matter since it is just the name of a class), you can create a single class that generates all of your test data. An important thing to remember is that you need to annotate that class with @isTest. This will ensure two things:

  • This class will not be included in coverage limits and it won't require unit tests to be written for it
  • This class can only be accessed by other test classes/methods.

The other important aspect to understand is how inserting a generic sObject works. You can utilize that to generate large sets of test data and limit your DML statements. This is crucial when trying to insert very large sets of objects (I have worked on several implementations with hundreds of objects that are necessary for testing). This will also allow you to do some cool things with Maps to then get those objects back from the test class. This will allow you to do something like:

public static Map<String, List<sObject>> generateTestModel(){
    List<sObject> objectsToAdd = new List<sObject>();
    List<Foo__c> foos = generateFoos();
    List<Stub__c> stubs = generateStubs();
    List<Widget__c> widgets = generateWidgets();
    objectsToAdd.add(foos);
    objectsToAdd.add(stubs);
    objectsToAdd.add(widgets);
    insert objectsToAdd;

    Map<String, List<sObject>> objectMapping = new Map<String, List<sObject>>();
    objectMapping.put('Foo__c', foos);
    objectMapping.put('Stub__c', stubs);
    objectMapping.put('Widget__c', widgets);
    return objectMapping;
}

For a more thorough breakdown of how I write my unit tests, check out my article.

| improve this answer | |
6

There's a new feature in Spring 15 allowing you to share test data across multiple tests inside the same test class.

Simply prepend the @testSetup annotation before one of your method in your test class and initialize data inside it. Then, this data will be available to every other test methods in your class - simply query them using SOQL query language.


Here's how to use it:

@isTest
private class CommonTestSetup {

    @testSetup static void setup() {
        // Create common test accounts
        List<Account> testAccts = new List<Account>();
        for(Integer i=0;i<2;i++) {
            testAccts.add(new Account(Name = 'TestAcct'+i));
        }
        insert testAccts;        
    }

    @isTest static void testMethod1() {
        // Get the first test account by using a SOQL query
        Account acct = [SELECT Id FROM Account WHERE Name='TestAcct0' LIMIT 1];
        // Modify first account
        acct.Phone = '555-1212';
        // This update is local to this test method only.
        update acct;

        // Delete second account
        Account acct2 = [SELECT Id FROM Account WHERE Name='TestAcct1' LIMIT 1];
        // This deletion is local to this test method only.
        delete acct2;

        // Perform some testing
    }

    @isTest static void testMethod2() {
        // The changes made by testMethod1() are rolled back and 
        // are not visible to this test method.        
        // Get the first account by using a SOQL query
        Account acct = [SELECT Phone FROM Account WHERE Name='TestAcct0' LIMIT 1];
        // Verify that test account created by test setup method is unaltered.
        System.assertEquals(null, acct.Phone);

        // Get the second account by using a SOQL query
        Account acct2 = [SELECT Id FROM Account WHERE Name='TestAcct1' LIMIT 1];
        // Verify test account created by test setup method is unaltered.
        System.assertNotEquals(null, acct2);

        // Perform some testing
    }

}

More information about it in the release notes:

http://docs.releasenotes.salesforce.com/en-us/spring15/release-notes/rn_apex_test_setup_methods.htm

| improve this answer | |
  • One question. Before spring 15, what we're doing is, we create private static variables, then initialize those variables in a static initializer block. This way, we can use those variables across the test methods (keeping things DRY because we only created the variables once). We also have the freedom to query them from within the test methods. With this at hand, what is the difference between the @testSetup approach and my approach? I would like to know if what we're doing is "best practice" or not. – SamuelDev Jul 21 '15 at 17:58
  • 1
    @Samuel_leumaS My understanding is that Salesforce will rollback the changes committed by each testMethod, while keeping testSetup data. This is probably allowing your tests to run faster and saving system resources. Also, it's a more straightforward way / best practice recommended by Salesforce from now on. – jpmonette Jul 23 '15 at 15:02
4

Methods defined in a class declared as public but annotated (at the class level) with @isTest will be accessible only within test methods, and will not need test coverage themselves.

You can make a helper class that does data setup for you, and then call these methods inside your test methods for a reusable implementation.

| improve this answer | |
1

I created this one today believe it or not. I had a basic version of this but, I finally expanded it on to make it more versitle.

It took me a while but, I finally got it down to something I was comfortable with (and in this case, not embarassed to share).

TestsObjectGeneratorUtility

@isTest
public class TestsObjectGeneratorUtility 
{
    public abstract class TestsObjectGenerator
    {
        protected Integer Iterable { get; set; }
        public GeneratorOverride OverwrittenGeneration { get; set; }
        protected abstract sObject AddSpecifiedObjectType();

        public TestsObjectGenerator()
        {
            this.Iterable = 0;
        }

        public List<sObject> GenerateTestsObjects(Integer NumberOfObjects, Boolean InsertNow)
        {
            List<sObject> ObjectList = new List<sObject>();

            for(; this.Iterable < NumberOfObjects; this.Iterable++)
                ObjectList.add( (this.OverwrittenGeneration != null) ? this.OverwrittenGeneration.RetrieveOverwrittensObject(this.Iterable) : this.AddSpecifiedObjectType());
            this.Iterable = 0;    
            if(InsertNow)
                INSERT ObjectList;
            return ObjectList;
        }

        public virtual void InitializeRequiredData() { }
        public virtual void DeletedRequiredData() { }
    }

    public abstract class GeneratorOverride
    {
        public abstract sObject RetrieveOverwrittensObject(Integer Iterable);   
    }

    private class ContactGenerator extends TestsObjectGenerator
    {
        public override sObject AddSpecifiedObjectType()
        {
            return new Contact(LastName = 'Testey ' + this.Iterable /*Fill in fields here*/);
        }
    }

    public static TestsObjectGenerator RetrieveGenerator(Schema.sObjectType sObjectTypeRequested)
    {
        if(sObjectTypeRequested.getDescribe().getName().equals('Contact'))
            return new ContactGenerator();
        return null;
    }
}

From that, you can expand it to accomidate any sObject of your choosing. Simply implement its support within the Simple Factory method. I added the overwrite ability because in some situations you may need to switch back and forth on how certain sObjects are created. I hold my overwrites in another class for simple access to them and encourage sharing for other team memebers tests.

Below is a demo on how you can use it:

@isTest
private class TestsObjectGeneratorUtilityDemo 
{
    public class ContactOverwriteDemo extends TestsObjectGeneratorUtility.GeneratorOverride
    {
        private String TestContactName { get; set; }

        public ContactOverwriteDemo (String TestName)
        {
            this.TestContactName = TestName;
        }

        public override sObject RetrieveOverwrittensObject(Integer Iterable)
        {
            return new Contact(Name = (this.TestContactName != null ? this.TestContactName : 'Test Overwritten Product' )  + String.valueOf(Iterable) /*Initialize other fields here*/);
        }
    }

    /*
        This demonstration shows the versitility of the TestsObjectGeneratorUtility. To best view the log, please change your log level to 'Info'.
    */
    static testMethod void TestsObjectGeneratorUtilityDemonstration() 
    {
        System.Debug(LoggingLevel.Info, '\n*****Let\'s start the TestsObjectGeneratorUtility Demonstration*****\n');

        TestsObjectGeneratorUtility.TestsObjectGenerator DemonstrationGenerator = TestsObjectGeneratorUtility.RetrieveGenerator(Contact.sObjectType);

        //Let's create a standard Contact first.
        System.Debug(LoggingLevel.Info, '\nLet\'s create a basic Contact sObject first.\n');
        System.Debug(LoggingLevel.Info, String.valueOf(DemonstrationGenerator.GenerateTestsObjects(1, false)));

        //Now let's try an overwritten Contact and insert two of them.
        TestsObjectGeneratorUtility.GeneratorOverride MyCustomOverride = new ContactOverwriteDemo ('ProgrammableMedley ');
        DemonstrationGenerator.OverwrittenGeneration = MyCustomOverride;

        System.Debug(LoggingLevel.Info, String.valueOf(DemonstrationGenerator.GenerateTestsObjects(2, true)));

        //To go back to the standard creation of Contact...
        DemonstrationGenerator.OverwrittenGeneration = null;    
    }
}
| improve this answer | |

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.