Friday, June 12, 2015

Using the Salesforce Metadata API to run a single test method in isolation

In my previous post The Holy Grail of Testing in Salesforce I covered my journey towards being able to execute a single Apex test method in isolation of the others defined in an Apex class. Here I'll cover how it actually works.

The Metadata API

In working with the Metadata API deploy() method as part of a continuous integration projection it dawned on me that I already had everything I needed to run a single test method in isolation.

As long as the test class and test method in question were public I could create a temporary wrapper test class that calls just the target test method.

For example, say the target was the Foo ApexClass and method bar(). Something like this, but with more actual testing and assertions going on:

@IsTest
public class Foo {
    public static testMethod void bar() {
        System.Debug(LoggingLevel.Debug,'Example Foo.bar() method');
        // Assertions and stuff
    }
}

Then to invoke this via a Metadata API deploy call all I need to do is create a wrapper class, the associated .cls-meta.xml, and package.xml to send in the zip payload. The wrapper class can be as simple as:

@IsTest
public class FooWrapper {
    public static testMethod void barWrapper() {
        Foo.bar();
    }
}

The most important part in deploy call is the DeployOptions.

  • Setting checkOnly to true will prevent any changes being actually deployed to the org. The FooWrapper class only exists to execute the test.
  • runTests(string[]) allows the deployment to run a specific test case. This should be set to the name of the wrapper test class that is being deployed. E.g. "FooWrapper"
    Once v34.0 is widely available the testLevel should also be set to RunSpecifiedTests.

The DebuggingHeader can be used to control the debug log contents that comes back in the response.

After starting the deploy process it is just a matter of monitoring the AsyncResult and DeployResult until it completes and then extracting the RunTestsResult.

The end result

Given an Apex testing class and the name of a test method it contains I can run a checkOnly deployment that will run just that test. As I mentioned before, the test in question needs to be public so that the wrapper class can access it. There are also likely to be issues if the @testSetup annotation is being used in the target class.

The future

It occured to me that this would also be a great mechanism for running anonymous apex in a testing context as well. The anonymous apex could easily be wrapped in a temporary class annotated with @IsTest and a test method. It could optionally specify @IsTest(SeeAllData=true) as well.

Additional test methods could be created in the wrapper class to target other test methods.


Update: Tooling API runTestsAsynchronous

Nate Lipke kindly pointed out to me that the Summer '15 release includes a runTestsAsynchronous method that accepts the target class Id's and a testMethods parameter "containing the name of a test method in the test class."

The REST version takes this as a POST request. The SOAP API also has this method, but it isn't apparent how to specify the target test methods yet.

If you are using the Force.com IDE v34.0.0.20150511 Beta Version you can use the new API to run a specific test method.

From the Summer '15 - Tooling API Updates

POST JSON arrays of test methods to runTestsAsynchronous
You can now POST a JSON array of test methods to the runTestsAsynchronous endpoint. Formerly, in POST methods, therunTestsAsynchronous endpoint accepted only comma-separated lists of class IDs.

Format:
POST
/runTestsAsynchronous/
   Body:
   {"tests":<tests array>}

Example <tests array>:

[{
  "classId" : "<classId 1>",
  "testMethods" : ["testMethod1","testMethod2","testMethod3"]
 },{
  "classId" : "<classId 2>",
  "testMethods" : ["testMethod1","testMethod2"]
}];