Take the 2-minute tour ×
Salesforce Stack Exchange is a question and answer site for Salesforce administrators, implementation experts, developers and anybody in-between. It's 100% free, no registration required.

We are making heavy use of stub and mock objects in our Apex tests. Most of the time we are defining the mocks as inner classes of our test classes. We have noticed, that test classes, which do contain such mock classes do show up at the Overall Code Coverage in the developer console. Such a test class does always have a code coverage of 0% and 0/zz lines covered.

I give a short example. Consider the following class of the business logic:

public virtual class Foo {

    public virtual String doSomething() {
        return anotherFunction('foo');
    }

    public virtual String anotherFunction(String value) {
        return value;
    }
}

A typical unit test would look like the following:

@isTest
private class FooTest {

    private class FooMock extends Foo {
        private Boolean anotherFunctionCalled;
        private String value;

        public override String anotherFunction(String value) {
            this.anotherFunctionCalled = true;
            this.value = value;

            return 'test';
        }
    }

    @isTest
    static void testDoSomething() {
        FooMock f = new FooMock();

        String result = f.doSomething();

        System.assertEquals('test', result);
        System.assertEquals(true, f.anotherFunctionCalled);
        System.assertEquals('foo', f.value);
    }

    @isTest
    static void testAnotherFunction() {
        Foo f = new Foo();

        String result = f.anotherFunction('blah');

        System.assertEquals('blah', result);   
    }
}

The Overall Code Coverage shows the following:

code coverage

Foo: 100% 4/4 Lines covered
FooTest: 0% 0/2 Lines covered

If you click on Apex Classes/Estimate your organization's code coverage you will get the surprising result of 66.67% (4/(2+4)) code coverage, which will not allow you to upload this code as a package.

How can we make sure, that the test classes will not be considered at the coverage estimation?

share|improve this question
    
Have you tried adding @isTest to the inner class definition? –  Alex Tennant May 6 at 19:00
    
@AlexTennant this results in the following compile error: Error: Compile Error: FooTest.FooMock: Only top-level non-exception class types can be marked as tests at line 5 column 19 –  Alexander Johannes May 6 at 19:06
    
I see your problem now, you're subclassing a non-test class. What is your reason for using this approach? –  Alex Tennant May 6 at 19:18
    
With this approach we are able to control every aspect of a unit test (e.g. input parameters, calls to other functions and their parameters). Without that approach it would impractical or even impossible to test several aspects of our business logic. For more information see here. –  Alexander Johannes May 6 at 19:25
    
I understand the concept. What I wanted to understand was your actual use case. Techniques that work in other environments are not always the best way to go about things in Salesforce due to the limitations of the platform and its architecture. Without knowing your use case it's hard to know if that is true in this case or not. –  Alex Tennant May 6 at 19:35

4 Answers 4

I'm guessing you'd want to move the FooMock definition to its own .class file. Keep only @isTest declared methods/classes in your test class file.

Or, perhaps that method needs to be declared static or global?

share|improve this answer
    
Why would i do that? The mock is only relevant for this particular unit test. This would severly affect the overall number of our classes in a particular project, which is approaching 1000 at the moment (still counting). –  Alexander Johannes May 6 at 19:01
    
Most times static is not desired, because it cannot be tested that easily. Global has to be used very carefully in the context of a commercial managed package. –  Alexander Johannes May 6 at 19:05
2  
Thank you for your input Alexander I see that Interface classes cannot be Annotated @IsTest. ( developer.salesforce.com/page/… ) So, it appears you do need test methods to cover your mock classes as well. –  mrBlaQ May 6 at 19:46
2  
Yeah, you just need to access f.anotherFunction('bar') from within testDoSomething() and you'll get your two lines of coverage you're missing. –  mrBlaQ May 6 at 19:55
    
@AlexanderJohannes I've encountered this too. It's rather unfortunate. Yes, they should not be top level classes and it makes a mess, but currently the metrics will be skewed unless you do. –  user320 May 6 at 20:02

I faced similar issue. I was able to fix it by adding "testMethod" keyword to the method.

Instead of this: @isTest static void testDoSomething() { }

Use this: @isTest static void testMethod testDoSomething() { }

share|improve this answer
    
This makes the problem even worse. If i exchange @isTest with testMethod, the unvovered lines in the test class grow from 0/2 to 0/6. –  Alexander Johannes May 21 at 7:24
up vote 1 down vote accepted

I finally managed to setup a new DevOrg with the class and test posted in the question.

Screenshot from the DevConsole:

DevConsole Code Coverage

Screenshot from the class overview:

Classes: code coverage

Screenshot from the upload as managed package:

Managed upload result

So, the described problem does not seem to have any real world impact.

share|improve this answer

We implemented the following in a managed package. This gives us testing flexibility.

3 classes MultiRequestMock, TestMultiRequestMock, testHttpCalloutMock

MultiRequestMock:

public class MultiRequestMock implements HttpCalloutMock {
Map<String, HttpCalloutMock> requests;

public MultiRequestMock(Map<String, HttpCalloutMock> requests) {
    this.requests = requests;
}
public HTTPResponse respond(HTTPRequest req) {

    boolean contains = requests.containsKey(req.getEndpoint());
    if (contains == true) {
        HttpCalloutMock mock = requests.get(req.getEndpoint());
        return mock.respond(req);
    } else {
        List<HttpCalloutMock> mocks = requests.values();
        if(mocks.size() > 0){
            return mocks[0].respond(req);
        }
        else {
            return null;
        }
    }
}
}

TestMultiRequestMock

@isTest
public class TestMultiRequestMock {
public static testmethod void testCallouts() {
    testHttpCalloutMock request1 = new testHttpCalloutMock(200, 'Complete', '[{"Name": "sForceTest1"}]');
    testHttpCalloutMock request2 = new testHttpCalloutMock(200, 'Complete', '[{"LastName": "Test Last Name"}]');

    Map<String, HttpCalloutMock> calloutMap = new Map<String, HttpCalloutMock>();
    calloutMap.put('http://domain1.com', request1);
    calloutMap.put('http://domain2.com',request2);
    system.assertEquals(2, calloutMap.size());

    HttpCalloutMock multiCalloutMock = new MultiRequestMock(calloutMap);

    Test.setMock(HttpCalloutMock.class, multiCalloutMock);
    multiCalloutTest();
    System.assertEquals('[{"Name": "sForceTest1"}][{"LastName": "Test Last Name"}]',resultTest);
}
private static String resultTest;
static void multiCalloutTest(){
    Http h1 = new Http();
    Httprequest req1 = new Httprequest();
    req1.setEndpoint('http://domain1.com');
    req1.setMethod('GET');
    HttpResponse res1 = h1.send(req1);

    Http h2 = new Http();
    Httprequest req2 = new Httprequest();
    req2.setEndpoint('http://domain2.com');
    req2.setMethod('GET');
    HttpResponse res2 = h2.send(req2);
    resultTest = res1.getBody()+res2.getBody();
}
}
}

testHttpCalloutMock:

    @isTest
public class testHttpCalloutMock implements HttpCalloutMock{
    protected  Integer code; 
    protected  String status; 
    protected  String body;
    public testHttpCalloutMock(Integer c, String s, String b){
        this.code=c;
        this.status=s;
        this.body=b;
    }
    public HttpResponse respond(HTTPRequest req){
        HttpResponse res = new HttpResponse();
        res.setStatus(status);
        res.setStatusCode(code);
        res.setBody(body);
        return res;
    }
}
share|improve this answer
    
I'm not talking about mocking HTTP requests. –  Alexander Johannes May 7 at 11:07

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.