2

Apex Rest returns 200 OK status code when directly access its Rest endpoint. However, in Apex Rest test class. RestResponse hooked in RestContext does not contains the status code (see below debug log).

Here is my test method. The containing class is also annotated with @isTest

@isTest
static void testCreateUser() {
    RestRequest req = new RestRequest();
    RestResponse res = new RestResponse();

    TestUtil.initTestData();

    User customerAdmin = [SELECT Id, AccountId FROM User WHERE username = 'testuser@acorp.com'];

    String requestBody = '{ "firstName": "Test", "lastName": "Case 2", "email": "testuser2@acorp.com" }';

    req.requestURI = URL.getSalesforceBaseUrl() + '/services/apexrest/MyId';
    req.httpMethod = 'POST';
    req.requestBody = Blob.valueOf(requestBody);

    Test.startTest();
    RestContext.request = req;
    RestContext.response = res;

    System.runAs(customerAdmin) {
        UserManagementRestController.createMyId();

        System.debug(req);
        System.debug(res);
    }

    Test.stopTest();
    // Something goes wrong here. No content in RestContext.response
    // Tempoary disable assertion on response
    //System.assertEquals(200, res.statusCode);

    List<User> users = [SELECT FirstName, LastName, Username, AccountId FROM User WHERE Email = 'testuser2@acorp.com'];
    System.assertEquals(1, users.size());

    User u = users[0];
    System.assertEquals('Test', u.FirstName);
    System.assertEquals('Case 2', u.LastName);
    System.assertEquals('testuser2@acorp.com', u.Username);
    System.assertEquals(customerAdmin.AccountId, u.AccountId);
}

My Apex REST class:

@RestResource(urlMapping='/MyId/*')
global class UserManagementRestController {

    @HttpPost
    global static void createMyId() {
        RestRequest req = RestContext.request;
        RestResponse res = RestContext.response;

        User requestUser = [SELECT Id, AccountId, Email FROM User WHERE Id = :UserInfo.getUserId()];

        // check if reqest user is customer admin
        Set<String> userPermissions = new Set<String>(UserPermissionRemoter.getUserPermission(requestUser.Id));
        if(!userPermissions.contains(UserPermissionMapper.customerAdminPermissionName)) {
            responseBadRequest('Only Customer Admin can perform this action', res);
            return;
        }

        MyId hid;
        try {
            hid = (MyId)JSON.deserialize(req.requestBody.toString(), MyId.class);
        } catch (JSONException e) {
            responseBadRequest(e.getMessage(), res);
            return;
        }
        System.debug('hid:');
        System.debug(hid);

        String email = hid.email;

        if(!isValidateEmail(email)) {
            responseBadRequest('Invalidate Email', res);
            return;
        }

        // check if new email is in the same domain with super user
        String[] splitEmail = email.split('@');
        String[] splitCurrentUserEmail = requestUser.Email.split('@');
        if(splitEmail[1].compareTo(splitCurrentUserEmail[1]) != 0) {
            responseBadRequest('Email domain mismatched. The new user email must have the same domain as the customer admin’s email.', res);
            return;
        }

        Savepoint createNewUserSavePoint = Database.setSavepoint();
        try {
            Contact newCt = new Contact();
            newCt.FirstName = hid.firstName;
            newCt.LastName = hid.lastName;
            newCt.Email = hid.email;
            newCt.AccountId = requestUser.AccountId;
            insert newCt;

            // Directly use corporate email
            String username = newCt.Email;

            // gen nickname
            DateTime currentTime = System.now();
            Long timeInMili = currentTime.getTime()/1000;
            String nickname = splitEmail[0]+timeInMili;

            // gen alias
            String alias = username;
            if(alias.length() > 8) {
                alias = alias.substring(0, 8);
            }

            User newUsr = new User();
            newUsr.FirstName = newCt.firstName;
            newUsr.LastName = newCt.lastName;
            newUsr.Email = newCt.email;
            newUsr.CommunityNickname = nickname;
            newUsr.profileId = getCommunityProfileId();
            newUsr.contactId = newCt.Id;
            newUsr.UserName = username;
            newUsr.Alias = alias;
            newUsr.TimeZoneSidKey = 'America/Los_Angeles';
            newUsr.LocaleSidKey = 'en_US';
            newUsr.EmailEncodingKey = 'ISO-8859-1';
            newUsr.LanguageLocaleKey = 'en_US';
            newUsr.IsActive = true;

            insert newUsr;
        } catch (system.Dmlexception e) {
            Database.rollback(createNewUserSavePoint);
            if(String.valueOf(e).contains('DUPLICATE_USERNAME')||String.valueOf(e).contains('DUPLICATE')){
                responseBadRequest('An account with this email already exists', res);
                return;
            }
            else{
                responseBadRequest(String.valueOf(e), res);
                return;
            }
        }
    }

    private static Boolean isValidateEmail(String email) {
        //Pattern p = Pattern.compile('^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$');
        //Matcher m = p.matcher(email);

        // according to SFDC support: https://help.salesforce.com/apex/HTViewSolution?id=000170904&language=en_US
        Pattern p = Pattern.compile('^[A-Z0-9._%+-/!#$%&\'*=?^_`{|}~]+@[A-Z0-9.-]+\\.[A-Z]{2,4}$');
        Matcher m = p.matcher(email.toUpperCase());
        return m.matches();
    }

    private static Id getCommunityProfileId() {
        Profile p = [SELECT Id FROM Profile WHERE Name = 'My Customer Portal User' limit 1][0];
        return p.Id;
    }

    private static void responseBadRequest(String message, RestResponse res) {
        res.responseBody = Blob.valueOf(message);
        res.statusCode = 400;
        return;
    }

    public class MyId {
        public String firstName;
        public String lastName;
        public String email;

        public Boolean isCustomerAdmin;
        public Boolean isActive;
    }
}

In the debug log we can see the RestRequest is correctly set. However the RestResponse does not have any value in it. Would expect statusCode=200

21:56:59.902 (25902510196)|USER_DEBUG|[30]|DEBUG|RestRequest:[headers={}, httpMethod=POST, params={}, remoteAddress=null, requestBody=Blob[77], requestURI=Url:[delegate=http://cs1.salesforce.com]/services/apexrest/MyId, resourcePath=null]
21:56:59.902 (25902571034)|USER_DEBUG|[31]|DEBUG|RestResponse:[headers={}, responseBody=null, statusCode=null]

So what is the reason of getting different result from the test class and the actual Rest endpoint? Any way to test the exact behaviour of Apex Rest endpoint in a unit test class?

  • Have you read this? salesforce.stackexchange.com/questions/4988/… – Boris Bachovski Jul 10 '15 at 6:35
  • Thanks for your reply and yes I have read it before asking this question. Is that really related to my question? My problem is to test the endpoint with different user. I can't see the post is helping me in this way. – Oscar Tang Jul 10 '15 at 7:10
  • And I am wondering why my question is -1 now. Can someone point out the problem and let me to improve my question asking skill next time? Thanks in advance. – Oscar Tang Jul 10 '15 at 7:11
  • 1) You can definitely test with runAs(); 2) The -1 is probably because you didn't include the essence of your post-annotated method – cropredy Jul 10 '15 at 16:20
  • @crop1645 Actually I use runAs() in many other test cases for a long time and they work well. However this is the first time for me to use it to test in Apex REST classes. And I don't think it works well. – Oscar Tang Jul 13 '15 at 3:02
2

With the help of @Eric I finally figure out the problem. It is not related to System.runAs(). runAs() works well in the test case.

Root cause: The response from RestContext.reponse in Test Class is different from what we are getting through the Apex REST endpoint.

For example:

@RestResource(urlMapping='/MyRest/*')
global class MyRestController {
    @HttpGet
    global static String myRest() {
        RestRequest req = RestContext.request;
        RestResponse res = RestContext.response;

        return 'From myRest';
    }
}

You will get

In Apex Test:

RestResponse:[headers={}, responseBody=null, statusCode=null]

When calling the Apex REST endpoint with REST client:

Status: 200 OK Body: "From myRest"

Salesforce Apex REST helps you to return status 200 and set the response body to the return String of myRest(). However this behaviour does not reflect in RestContext.response of Apex Rest test case. You have to explicitly set it in the code if you want to test the response...

@RestResource(urlMapping='/MyRest/*')
global class MyRestController {
    @HttpGet
    global static String myRest() {
        RestRequest req = RestContext.request;
        RestResponse res = RestContext.response;

        // set the status code and response body for test case
        res.statusCode = 200;
        res.responseBody = Blob.valueOf('From myRest');

        return 'From myRest';
    }
}

You will get

In Apex Test:

RestResponse:[headers={}, responseBody=Blob[11], statusCode=200]
RestResponse.responseBody.toString():From myRest
0

You are debugging properties that have never been assigned or set

And you test should be

res = UserManagementRestController.createMyId();

And your debugs should be:

System.debug(req);
System.debug(res);

or simply system.debug(UserManagementRestController.createMyId());

BUT, your post could return something like a string. Try changing void to string and simply return 'test' and see what you get.

There is no response as there is NO REST communication going on. If you want to test the response, all you can do is use test.setMock and implement the appropriate interface. You can also test from the dev console by actually constructing the http, send the request, and evaluate the response

  • I think it is normal to return nothing? From the official guide developer.salesforce.com/docs/atlas.en-us.apexcode.meta/… , I see there is an example to declare the return type as void. – Oscar Tang Jul 14 '15 at 4:34
  • Anyway I have tried your suggest: global static RestResponse createMyId() { But it pumps the following error during compile: Invalid type for Http* method: System.RestResponse – Oscar Tang Jul 14 '15 at 4:35
  • @OscarTang - Ok, maybe you can leave it void, BUT read the last part of my answer. You are debugging the RestContext.Response which is null because nothing has been assigned to it, res is null because nothing has been assigned to it. At the very least leave it void and debug UserManagementRestController.createMyId(); see updated answer. – Eric Jul 14 '15 at 5:11
  • For System.debug(UserManagementRestController.createMyId()); it throws: Method does not exist or incorrect signature: System.debug(void) For res = UserManagementRestController.createMyId();, it throws: Illegal assignment from void to RestResponse – Oscar Tang Jul 14 '15 at 6:29
  • @OscarTang - hence why you need to return something. You are confusing issues with your debug vs the return. You initial code is simply always going to debug null regardless of what you do because it is coded wrong. – Eric Jul 14 '15 at 15:16

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.