Request for feedback: “Unit Test” Custom Object idea

I’m toying with the idea of adding a “unit test” custom object to apex-lang and I’d really like to know your thoughts. If possible, please read details below and leave a comment with your feedback.

My utopian vision for apex-lang is that it’s unit tests would succeed in any salesforce org capable of executing apex code. I’ve taken great care to make the unit tests as portable as possible; however, one area where this falls short is unit tests which must do DML. Those unit tests exercise the generic SObject handling methods and per the widely accepted pattern of creating data versus querying for it, they create records -> usually on Account or Contact. The problem with this approach of course, is these unit tests will fail when a required field is added to objects like Account or Contact.

Which brings me to my idea: inclusion of a custom object in apex-lang whose sole purpose is make unit tests which require SObject instances more predictable. The custom object would never be used in any practical sense other than in unit tests. I would also add as many fields as there are distinct field types; in other words, there’d be a string field, a date field, a multi-select picklist field and so on. In addition to making apex-lang unit tests more predictable, I think there’s a good use case for consumers of apex-lang who write generic SObject handling code. I do this often, like in the geocoding toolkit I created for a Cloudspokes challenge.

The first downside is I’d be violating another vision of mine for apex-lang: that it consist of apex code and apex code only. Probably not a big deal to throw in a custom object and I need to just get over it. The other downside I can think of is apex-lang would be burning a custom object. But it seems to me that most orgs have plenty to spare so that’s probably not a big deal either.

Thoughts?

Posted in Uncategorized. 3 Comments »

apex-lang 1.17 released

I’m happy to announce I’ve released version 1.17 of apex-lang! It’s been a while since I’ve published a release so this one has a substantial amount of new features.

Links for apex-lang version 1.17

Changes since 1.15

Once you install version 1.17, any of the following code snippets can be run in your org. Note: the snippets assume you installed the managed package; if you instead installed the code directly to your org, remove the “al.” prefix before running.

DatabaseUtils

New class that allows records to be easily retrieved by object id(s) alone; it even allows you to do “select *” if you so choose.  The query method has magic behind it to determine object name of the corresponding id(s).

//insert an account
final Account x = new Account(Name='Test 123', NumberOfEmployees=1);
insert x;

//re-load the account by id only, following call is analogous to select *
final Account y = (Account) al.DatabaseUtils.query(x.id);
System.assertEquals(x.id,y.id);
System.assertEquals(x.name,y.name);
System.assertEquals(x.NumberOfEmployees,y.NumberOfEmployees);

//of course, the more responsible thing to do is to specify the fields you want
final Account z = (Account) al.DatabaseUtils.query(x.id,new Set{'name'});
System.assertEquals(x.id,z.id);
System.assertEquals(x.name,z.name);
//uncomment the following row and you'll get an exception because NumberOfEmployees wasn't retrieved
//System.assertEquals(x.NumberOfEmployees,z.NumberOfEmployees);

//the magic behind this code is the retrieveObjectName() method
//it's probably useful in other context as well
final String objectName = al.DatabaseUtils.retrieveObjectName(''+x.id);
System.assertEquals('account',StringUtils.lowerCase(objectName));

HttpUtils

A new class which enables 100% coverage of web service callouts.  Per this note, the Http.send() method cannot be executed in unit tests.  It’s really a shame that salesforce hasn’t created a mechanism for getting around this similar to Test.setFixedSearchResults() for SOSL queries.  I thought they would’ve by now but alas, no progress.  I’ve seen lots of good ways for getting around this; however, most involve muddying up your real code with test related code (by real code, I mean code that isn’t a unit test); in other words, there’s an if(Test.isRunningTest()) check somewhere in your real code.  I’d much rather keep all my unit testing logic in my unit tests themselves and that’s precisely what HttpUtils allows you to do.  See following example for an explanation.


//save this class and execute unit tests
public class MyService{
    private static final String TEST_RESPONSE = 'a response string - might be xml, json, etc...';
    private HttpRequest request;

    public MyService(){
        request = new HttpRequest();
        request.setEndpoint('http://somewhere.com');
        request.setMethod('GET');
    }

    public String theWayYoureProbablyInvokingCallout(){
        String response = null;
        //following line is such a hack; this is test code in your real code!
        if(Test.isRunningTest()) response = TEST_RESPONSE;
        //but you have to do it because the following line can't be covered #insanity
        else response = (new Http()).send(request).getBody();
        return response;
    }

    public String sameResultButEasierToTest(){
        return al.HttpUtils.sendReturnBody(request);
    }

    private static testmethod void test_theWayYoureProbablyInvokingCallout(){
        System.assertEquals(TEST_RESPONSE,new MyService().theWayYoureProbablyInvokingCallout());
    }

    private static testmethod void test_sameResultButEasierToTest(){
        //Note the call to push test here.  Your pushing your expected result immediately before you check for it.
        al.HttpUtils.pushTest(TEST_RESPONSE);
        System.assertEquals(TEST_RESPONSE,new MyService().sameResultButEasierToTest());
    }

}

Here are the send methods you can invoke on HttpUtils:

  • global static HttpResponse send(HttpRequest request)
  • global static String sendReturnBody(HttpRequest request)
  • global static Dom.Document sendReturnDocument(HttpRequest request)
  • global static XmlStreamReader sendReturnReader(HttpRequest request)

SObjectSortByFieldComparator

Another new class that simplifies doing in-memory sort of SObjects (vs using ArrayUtils.qsort).  You simply give the class a list of SObjects and tell it the field to sort against.


//sort a list of accounts on field NumberOfEmployees
List unsorted = new List{
     new Account(name='A',NumberOfEmployees=2)
    ,new Account(name='D',NumberOfEmployees=3)
    ,new Account(name='C',NumberOfEmployees=0)
    ,new Account(name='B',NumberOfEmployees=1)
};
List sorted = al.SObjectSortByFieldComparator.qsort(unsorted,'NumberOfEmployees');
System.assertNotEquals(null,sorted);
System.assertEquals(4,sorted.size());
System.assertEquals(0,sorted.get(0).get('NumberOfEmployees'));
System.assertEquals(1,sorted.get(1).get('NumberOfEmployees'));
System.assertEquals(2,sorted.get(2).get('NumberOfEmployees'));
System.assertEquals(3,sorted.get(3).get('NumberOfEmployees'));

//you could even mix SObjects so long as they share a field
unsorted = new List{
     new Account(name='A')
    ,new Opportunity(name='D')
    ,new Document(name='C')
    ,new Campaign(name='B')
};
//sort is on name field by default; false means sort descending
sorted = al.SObjectSortByFieldComparator.qsort(unsorted, false);
System.assertNotEquals(null,sorted);
System.assertEquals(4,sorted.size());
System.assertEquals('D',sorted.get(0).get('name'));
System.assertEquals('C',sorted.get(1).get('name'));
System.assertEquals('B',sorted.get(2).get('name'));
System.assertEquals('A',sorted.get(3).get('name'));

MapUtils

Added methods for doing trim, lowerCase, upperCase en masse to keys or values on a map.

al.MapUtils.assertEquals(
  new Map{'a'=>'Y','b'=>'Z'}
  ,al.MapUtils.lowerCaseKeys(new Map{'A'=>'Y','B'=>'Z'})
);

al.MapUtils.assertEquals(
  new Map{'a'=>'Y','b'=>'Z'}
  ,al.MapUtils.upperCaseValues(new Map{'a'=>'y','b'=>'z'})
);

SetUtils

Added methods to make switching between lists and sets easier; trim, lowerCase, upperCase en masse methods; new method pluckString.

//switching between String sets and lists
final Set myStrSet1 = new Set{'a','b'};
final Set myStrSet2 = al.SetUtils.listToSet(al.SetUtils.setToList(myStrSet1));
System.assertEquals(myStrSet1,myStrSet2);

//switching between SObject sets and lists
final Set mySObjSet1 = new Set{new Account(name='a'),new Account(name='b')};
final Set mySObjSet2 = al.SetUtils.listToSet(al.SetUtils.setToList(mySObjSet1));
System.assertEquals(mySObjSet1,mySObjSet2);

//plucking an attribute from a set of SObjects
System.assertEquals(myStrSet1,al.SetUtils.pluckString(mySObjSet1,'name'));

//trim and lower case all values in a set
System.assertEquals(myStrSet1,al.SetUtils.lowerCase(al.SetUtils.trim(new Set{'  A\n\t  ','  B '})));

StringUtils

Added a new stripMarkup() method which will remove all HTML/XML markup from a string.

System.assertEquals(null, al.StringUtils.stripMarkup(null));
System.assertEquals('', al.StringUtils.stripMarkup(''));
System.assertEquals('Title! ABC 123', al.StringUtils.stripMarkup('  Title!   ABC 123  '));
System.assertEquals('Title! ABC 123', al.StringUtils.stripMarkup('<html><body><h1>Title!</h1><p>ABC 123</p></html>'));

Note about test coverage

I think one of the best decisions salesforce made when designing Apex was to require 75% code coverage. Salesforce doesn’t get enough good publicity for this because that decision has forced many folks – myself certainly included – to become better developers. With the unit tests for apex-lang, I decided to buy into the Test Driven Development philosophy even further. My standard has been (1) every line must be tested (100% code coverage) and (2) the unit tests actually verify functionality. That latter part is key because code coverage in and of itself really doesn’t buy you anything – it’s the verification that adds value.

That being said, I wanted to note an exception to this rule that I’ve introduced in 1.17 and that’s the HttpUtils class. Like I said above, the Http.send() method cannot be called in a unit test; however, in order for the HttpUtils class to be of value, it obviously needs to call it. So, in the interest of making unit testing eaiser, I’ve sacrificed some of apex-lang’s code coverage by a small number: I believe HttpUtils is 98% covered. But it’s not 100% so that’s why I mentioned it.

Posted in Uncategorized. 5 Comments »

apex-lang 1.15 released

Thank you Joel Dietz for contributing to apex-lang!  Joel added aggregate functions to SoqlBuilder and here is an explanation of those changes: http://d3developer.com/2011/01/11/soql-builder-enhancements/.

For anyone else that would like to contribute to apex-lang, please don’t hesitate to contact me.

Posted in Uncategorized. No Comments »

Want to hear about apex-lang at Dreamforce 2010?

Then please vote for it here!

Posted in Uncategorized. No Comments »