Setting up the RestContext manually in this case is a bit like Test.setCurrentPage if your Apex Controller code looks for the current page for what it needs. And so in this case it helps emulate what your service is expecting at runtime.
In the case of designing Rest services, they receive parameters via a number of inputs. A key one is the URI (as parts of this are often dynamic to contextualise the call much like a web page URL). In this case the member_name_c filter is extracted from the URI (you can also use URI parameters, see the docs on RestRequest methods for a note on testing).
So yes you can call the method direct, but if it needs any context (via its own references to RestContext) you will still need the RestContext setup before you call the methods. You mention above that you obtain the same code coverage, do the asserts still pass, as it looks like if no member_name__c filter is resolved from the URI it would throw an exception and thus the assertions relating to the response should fail?
Thoughts on Best Practice: As regards best practice, since Apex REST services now handle most Apex types, for more rich servies you create via annotations. The platform then handles all the serialisation and deserialisation for you. I don't see there is much need to really use RestContext beyond using it to emulate the URI (and any parameters on it if you need this) the client is calling you on. The rest of the time, as per standard Apex Unit tests you can just pass in parameters directly and handling responses and exceptions directly from the methods you write in the standard way.
Manual REST Services: That said, if you do want to perform your own low level serialisation and deserialisation by handling this in your Apex REST method. For example custom exception handling to the REST response or some serialisation of your own. The RestContext will be very useful both before and after to test these kind of services. However I would say unless you have some very good reason, you should use the excellent default behaviour of simply expressing your needs via the method signature with annotations.
EDITED: It works sometimes without any problem (even with the oldest Api 22.0 that supports RestResource) and sometimes incorrectly.
That depends on the order of classes sorted by the time of the last modification.
If you edit the class REST1.cls or generally that with the shortest urlMapping length (add a comment into) it starts to work as you expect.
It is not exactly incorrect, because *
matches any string with any number of /
. It seems that the text "the patterns with wildcards... select the longest (by string length)" should be understand only "the longest string before the wildcard". The order of compared matching is not explicitely defined by docs if these parts are the same.
They mean only: /word/*
is a longer and better match than /*
.
They want to provide a fast service. Therefore they probably need firstly to do a coarse match by an index, then to try classes ordered by this index until the first match is found. If you can find a good index expression than it can be probably fixed, but it is much complicated by rules about the final slash. I'm skeptical of anything, except for asking a possible very small improvement in documentation.
Tested code exemples:
REST1
@RestResource(urlmapping='/invoices/*/')
global class REST1 {
@HttpGet
global static String getClassName() {
return 'REST1';
}
}
REST2
@RestResource(urlmapping='/invoices/*/totals/')
global class REST2 {
@HttpGet
global static String getClassName() {
return 'REST2';
}
}
REST3
@RestResource(urlmapping='/invoices/*/related/')
global class REST3 {
@HttpGet
global static String getClassName() {
return 'REST3';
}
}
It is better and more universal to use urlMapping
string without the final slash, but is is not important for this question. Example:
@RestResource(urlmapping='/invoices/*')
It can catch both /invoices/something/
and /invoices/something
, while the mapping with the final slash requires only this one. Every example in Apex docs (e.g. the qouted above) is without any final slash.
**EDITED ** because the problem was not initially reproducible.
Best Answer
The
(Map<String, Object>)
bit is called type casting. It forces something of one type to be treated as if it were a different type.In the case of
JSON.deserializeUntyped()
, the method returns a result of typeObject
, but that type is functionally useless to us. The method returns anObject
because JSON can start as either a list or a map. Since there's no way to know which it'll be until you try to deserialize some JSON, Salesforce needs to use a return type that can cover both cases. In this case, the only thing that would work is theObject
type.To be able to access the deserialized data, we need to use some other type. The type cast to
Map<String, Object>
is basically us saying yes, I know this is technically anObject
, but treat it as aMap<String, Object>
instead, trust me on this.Type casting only works if the types are compatible (i.e. one type is derived from the other).
Account
as anSObject
(up-casting, since you're going from a more specific type (Account) to its less specific, parent type (SObject)).SObject
as anAccount
(down-casting, since you're changing to a more specific type).String
andInteger
(though we can useInteger.valueOf()
to convert a String to an Int, and useString.valueOf()
to convert an Int to a String)