APEX, Unit Test, Callout No Response with Static Resource - json

Bit stuck on another one i'm afraid, i am trying to write a unit test for a bulk APEX class.
The class has a calllout to the google api, so i have created a static resource which i am feeding in via a mock, so i can complete testing of processing the JSON that is returned. However for some reason the response is always empty.
Now the very odd thing is that if i use exactly the same callout/JSON code, and the same mock code on a previous #future call, then it does return fine.
Here is the class:
global class mileage_bulk implements Database.Batchable<sObject>,
Database.AllowsCallouts
{
global Database.QueryLocator start(Database.BatchableContext BC)
{
String query = 'SELECT Id,Name,Amount,R2_Job_Ref__c,R2_Shipping_Post_Code__c,Shipping_Postcode_2__c FROM Opportunity WHERE R2_Shipping_Post_Code__c != null';
return Database.getQueryLocator(query);
//system.debug('Executing'+query);
}
global void execute(Database.BatchableContext BC, List<Opportunity> scope)
{
system.debug(scope);
for(Opportunity a : scope)
{
String startPostcode = null;
startPostcode = EncodingUtil.urlEncode('HP27DU', 'UTF-8');
String endPostcode = null;
String endPostcodeEncoded = null;
if (a.R2_Shipping_Post_Code__c != null){
endPostcode = a.R2_Shipping_Post_Code__c;
Pattern nonWordChar = Pattern.compile('[^\\w]');
endPostcode = nonWordChar.matcher(endPostcode).replaceAll('');
endPostcodeEncoded = EncodingUtil.urlEncode(endPostcode, 'UTF-8');
}
Double totalDistanceMeter = null;
Integer totalDistanceMile = null;
String responseBody = null;
Boolean firstRecord = false;
String ukPrefix = 'UKH';
if (a.R2_Job_Ref__c != null){
if ((a.R2_Job_Ref__c).toLowerCase().contains(ukPrefix.toLowerCase())){
system.debug('Is Hemel Job');
startPostcode = EncodingUtil.urlEncode('HP27DU', 'UTF-8');
} else {
system.debug('Is Bromsgrove Job');
startPostcode = EncodingUtil.urlEncode('B604AD', 'UTF-8');
}
}
// build callout
Http h = new Http();
HttpRequest req = new HttpRequest();
req.setEndpoint('http://maps.googleapis.com/maps/api/directions/json?origin='+startPostcode+'&destination='+endPostcodeEncoded+'&units=imperial&sensor=false');
req.setMethod('GET');
req.setTimeout(60000);
system.debug('request follows');
system.debug(req);
try{
// callout
HttpResponse res = h.send(req);
// parse coordinates from response
JSONParser parser = JSON.createParser(res.getBody());
responseBody = res.getBody();
system.debug(responseBody);
while (parser.nextToken() != null) {
if ((parser.getCurrentToken() == JSONToken.FIELD_NAME) &&
(parser.getText() == 'distance')){
parser.nextToken(); // object start
while (parser.nextToken() != JSONToken.END_OBJECT){
String txt = parser.getText();
parser.nextToken();
//system.debug(parser.nextToken());
//system.debug(txt);
if (firstRecord == false){
//if (txt == 'text'){
//totalDistanceMile = parser.getText();
system.debug(parser.getText());
//}
if (txt == 'value'){
totalDistanceMeter = parser.getDoubleValue();
double inches = totalDistanceMeter*39.3701;
totalDistanceMile = (integer)inches/63360;
system.debug(parser.getText());
firstRecord = true;
}
}
}
}
}
} catch (Exception e) {
}
//system.debug(accountId);
system.debug(a);
system.debug(endPostcodeEncoded);
system.debug(totalDistanceMeter);
system.debug(totalDistanceMile);
// update coordinates if we get back
if (totalDistanceMile != null){
system.debug('Entering Function to Update Object');
a.DistanceM__c = totalDistanceMile;
a.Shipping_Postcode_2__c = a.R2_Shipping_Post_Code__c;
//update a;
}
}
update scope;
}
global void finish(Database.BatchableContext BC)
{
}
}
and here is the test class;
#isTest
private class mileage_bulk_tests{
static testMethod void myUnitTest() {
Opportunity opp1 = new Opportunity(name = 'Google Test Opportunity',R2_Job_Ref__c = 'UKH12345',R2_Shipping_Post_Code__c = 'AL35QW',StageName = 'qualified',CloseDate = Date.today());
insert opp1;
Opportunity opp2 = new Opportunity(name = 'Google Test Opportunity 2',StageName = 'qualified',CloseDate = Date.today());
insert opp2;
Opportunity opp3 = new Opportunity(name = 'Google Test Opportunity 3',R2_Job_Ref__c = 'UKB56789',R2_Shipping_Post_Code__c = 'AL35QW',StageName = 'qualified',CloseDate = Date.today());
insert opp3;
StaticResourceCalloutMock mock = new StaticResourceCalloutMock();
mock.setStaticResource('googleMapsJSON');
mock.setStatusCode(200); // Or other appropriate HTTP status code
mock.setHeader('Content-Type', 'application/json'); // Or other appropriate MIME type like application/xml
//Set the mock callout mode
Test.setMock(HttpCalloutMock.class, mock);
system.debug(opp1);
system.debug(opp1.id);
//Call the method that performs the callout
Test.startTest();
mileage_bulk b = new mileage_bulk();
database.executeBatch((b), 10);
Test.stopTest();
}
}
Help greatly appreciated!
Thanks
Gareth

Not certain what 'googleMapsJSON' looks like, perhaps you could post for us.
Assuming your mock resource is well formatted, make sure the file extension is ".json" and was saved with UTF-8 encoding.
If #2 does not work, you should try saving your resource as .txt - I've run in to this before where it needed a plain text resource but expected application/json content type
Be certain that the resource name string you are providing has the same casing as the name of the resource. It is case sensitive.
Are you developing on a namespaced package environment? Try adding the namespace to the resource name if so.
Otherwise, your code looks pretty good at first glance.

Related

TableContinuationToken not getting Deserialised from JSON correctly

I am having trouble trying to retrieve large datasets from Azure TableStorage. After several attempts at trying to get it in one go I have given up and am now using the TableContinuation Token, which is now not getting Deserialized correctly.The object is getting created but all the Next... values (i.e. NextRowKey, NextPartitionKey, etc are NULL, when the in stringresponse that gets created you can see the values it should be populating with...
The class I am passing contains a list of objects and the token
public class FlorDataset
{
public List<FlorData> Flors { get; set; }
public TableContinuationToken Token { get; set; }
}
The controller code is not exactly rocket science either....
[HttpGet, Route("api/list/{token}")]
public IHttpActionResult FindAll(string token)
{
try
{
TableContinuationToken actualToken = token == "None"
? null
: new TableContinuationToken()
{
NextPartitionKey = NextPartition,
NextRowKey = token,
NextTableName = NextTableName
};
var x = Run(actualToken);
Flors = x.Flors;
actualToken = x.Token;
NextTableName = actualToken.NextTableName;
NextPartition = actualToken.NextPartitionKey;
return Flors != null
? (IHttpActionResult)new IsoncOkResult<FlorDataset>(x, this)
: NotFound();
}
catch (Exception ex)
{
Trace.TraceError(ex.ToString());
return NotFound();
}
}
private FlorDataset Run(TableContinuationToken token)
{
return _repo.GetAllByYear("2016", token) as FlorDataset;
}
The calling code, which calls my fairly standard Web API 2 Controller is:
do
{
try
{
HttpResponseMessage response = null;
if (string.IsNullOrEmpty(token.NextRowKey))
{
response = await client.GetAsync("api/list/None");
}
else
{
response = await client.GetAsync($"api/list/{token.NextRowKey}");
}
if (response.IsSuccessStatusCode)
{
var stringresponse = await response.Content.ReadAsStringAsync();
var ds = JsonConvert.DeserializeObject<FlorDataset>(stringresponse);
token = ds.Token;
Flors.AddRange(ds.Flors);
}
else
{
token = null;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
token = null;
}
} while (token != null);
Okay, this is not the greatest solution, but it's the only thing that works so far in case anyone else is trying the same and stumbling across my question....
In the calling code bit you do a horrible bit of string replacement before you do the deserialisation.... I actually feel dirty just posting this, so if anyone comes up with a better answer, please feel free to share.....
if (response.IsSuccessStatusCode)
{
var stringresponse = await response.Content.ReadAsStringAsync();
stringresponse = stringresponse.Replace(">k__BackingField", "");
stringresponse = stringresponse.Replace("<", "");
var ds = JsonConvert.DeserializeObject<FlorDataset>(stringresponse);
token = ds.Token;
Flors.AddRange(ds.Flors);
}
Not nice, not pretty, but does work!!!! :-D Going to wash my fingers with bleach now!!!

Json data serialized with JsonConvert.SerializeObject is always string in ASP.NET Web API

I am developing a ASP.NET MVC Web Api. Project. I am returning data with JSON format. Before I return data to user I serialize data using JsonConvert.SerializeObject to change their json property names.My code return data in JSON format. But with an issue. That is it always return data into string even if the data is array or object.
This is my action method that returns json.
public HttpResponseMessage Get()
{
IEnumerable<Region> dbRegions = regionRepo.GetCachedRegions();
List<ContentRegion> regions = new List<ContentRegion>();
if(dbRegions!=null && dbRegions.Count()>0)
{
foreach(var region in dbRegions)
{
ContentRegion contentRegion = new ContentRegion
{
Id = region.Id,
ImageUrl = Url.AbsoluteContent(region.ImagePath),
SmallImageUrl = (String.IsNullOrEmpty(region.ImagePath))?null:Url.AbsoluteContent(CommonHelper.GetImageUrl(region.ImagePath,AppConfig.SmallThumbSuffix)),
MediumImageUrl = (String.IsNullOrEmpty(region.ImagePath))?null:Url.AbsoluteContent(CommonHelper.GetImageUrl(region.ImagePath,AppConfig.MediumThumbSuffix)),
Name = region.Name,
MmName = region.MmName,
Description = region.Description,
MmDescription = region.MmDescription,
Latitude = region.Latitude,
Longitude = region.Longitude
};
regions.Add(contentRegion);
}
}
string json = JsonConvert.SerializeObject(regions);
if(!string.IsNullOrEmpty(json))
{
json = json.Trim(new char[] { '"' });
}
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ObjectContent(json.GetType(),json,Configuration.Formatters.JsonFormatter)
};
}
Actually this code should return Json array. But when I parse data from client (from Android using Volley). It cannot be parsed into Json Array.
This is the data I get:
As you can see the double quote both in the beginning and at the end. The reason I cannot parse it into array in Volley is it is returning as a string because of that double. How can I serialize it trimming that quote? I used trim, but not removed.
You are unnecessarily complicating things. In Web API you can return JSON just by returning any object inside the built-in methods, the framework will serialize it for you.
public IHttpActionResult Get()
{
IEnumerable<Region> dbRegions = regionRepo.GetCachedRegions();
List<ContentRegion> regions = new List<ContentRegion>();
if(dbRegions != null && dbRegions.Count() > 0) {
foreach(var region in dbRegions)
{
ContentRegion contentRegion = new ContentRegion
{
Id = region.Id,
ImageUrl = Url.AbsoluteContent(region.ImagePath),
SmallImageUrl = (String.IsNullOrEmpty(region.ImagePath))?null:Url.AbsoluteContent(CommonHelper.GetImageUrl(region.ImagePath,AppConfig.SmallThumbSuffix)),
MediumImageUrl = (String.IsNullOrEmpty(region.ImagePath))?null:Url.AbsoluteContent(CommonHelper.GetImageUrl(region.ImagePath,AppConfig.MediumThumbSuffix)),
Name = region.Name,
MmName = region.MmName,
Description = region.Description,
MmDescription = region.MmDescription,
Latitude = region.Latitude,
Longitude = region.Longitude
};
regions.Add(contentRegion);
}
}
return Ok(regions);
}
As an aside: from what I can see you are mapping manually your domain objects into DTOs: take into consideration the use of an automatic mapping mechanism like AutoMapper.
I am not sure this is the best solution or not. I solved the problem using this way.
This is my action method
public HttpResponseMessage Get()
{
try
{
IEnumerable<Region> dbRegions = regionRepo.GetCachedRegions();
List<ContentRegion> regions = new List<ContentRegion>();
if (dbRegions != null && dbRegions.Count() > 0)
{
foreach (var region in dbRegions)
{
ContentRegion contentRegion = new ContentRegion
{
Id = region.Id,
ImageUrl = Url.AbsoluteContent(region.ImagePath),
SmallImageUrl = (String.IsNullOrEmpty(region.ImagePath)) ? null : Url.AbsoluteContent(CommonHelper.GetImageUrl(region.ImagePath, AppConfig.SmallThumbSuffix)),
MediumImageUrl = (String.IsNullOrEmpty(region.ImagePath)) ? null : Url.AbsoluteContent(CommonHelper.GetImageUrl(region.ImagePath, AppConfig.MediumThumbSuffix)),
Name = region.Name,
MmName = region.MmName,
Description = region.Description,
MmDescription = region.MmDescription,
Latitude = region.Latitude,
Longitude = region.Longitude
};
regions.Add(contentRegion);
}
}
string json = JsonConvert.SerializeObject(regions);
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(json, Encoding.Default, "application/json")
};
}
catch
{
return Request.CreateResponse(HttpStatusCode.InternalServerError);
}
}
It's not required to convert object to json string.
You can try :
return Request.CreateResponse<List<ContentRegion>>(HttpStatusCode.OK,regions);
Not tested.
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
Use this line in your WebApiConfig.
And here your code should be
public HttpResponseMessage Get()
{
IEnumerable<Region> dbRegions = regionRepo.GetCachedRegions();
List<ContentRegion> regions = new List<ContentRegion>();
HttpResponseMessage temp = ControllerContext.Request.CreateResponse(HttpStatusCode.OK, "");
if (dbRegions != null && dbRegions.Count() > 0)
{
foreach (var region in dbRegions)
{
ContentRegion contentRegion = new ContentRegion
{
Id = region.Id,
ImageUrl = Url.AbsoluteContent(region.ImagePath),
SmallImageUrl = (String.IsNullOrEmpty(region.ImagePath)) ? null : Url.AbsoluteContent(CommonHelper.GetImageUrl(region.ImagePath, AppConfig.SmallThumbSuffix)),
MediumImageUrl = (String.IsNullOrEmpty(region.ImagePath)) ? null : Url.AbsoluteContent(CommonHelper.GetImageUrl(region.ImagePath, AppConfig.MediumThumbSuffix)),
Name = region.Name,
MmName = region.MmName,
Description = region.Description,
MmDescription = region.MmDescription,
Latitude = region.Latitude,
Longitude = region.Longitude
};
regions.Add(contentRegion);
}
}
temp = ControllerContext.Request.CreateResponse(HttpStatusCode.OK, regions);
return temp;
//string json = JsonConvert.SerializeObject(regions);
//if (!string.IsNullOrEmpty(json))
//{
// json = json.Trim(new char[] { '"' });
//}
//return new HttpResponseMessage(HttpStatusCode.OK)
//{
// Content = new ObjectContent(json.GetType(), json, Configuration.Formatters.JsonFormatter)
//};
}

How to return an Error from a generic method

The title doesn't describe the issue very well so let me explain my problem.
I have a generic function to consume API's via HTTP GET calls. Which looks like this:
public async static Task<T> GetAsync<T>(string Base_Url,string relative_URL, Utility.UriExtensions.NameValueCollection Params, Utility.UriExtensions.NameValueCollection headers = null)
{
Uri CompleteURL = new Uri(Base_Url + relative_URL, UriKind.Absolute);
if (Params != null)
CompleteURL = Utility.UriExtensions.CreateUriWithQuery(CompleteURL, Params);
if(headers!=null)
{
foreach(KeyValuePair<string,string> k in headers)
{
if (ApiHttpClient.DefaultRequestHeaders.ContainsKey(k.Key))
ApiHttpClient.DefaultRequestHeaders[k.Key] = k.Value;
else
ApiHttpClient.DefaultRequestHeaders.Add(k.Key, k.Value);
}
}
Debug.WriteLine("GET : " + CompleteURL);
using (var response = await ApiHttpClient.GetAsync(CompleteURL).AsTask(cancellationToken.Token).ConfigureAwait(false))
{
string responseData="";
if (response.IsSuccessStatusCode)
{
responseData = await response.Content.ReadAsStringAsync();
Debug.WriteLine(responseData);
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.DateParseHandling = DateParseHandling.DateTime;
settings.DefaultValueHandling = DefaultValueHandling.Populate;
settings.NullValueHandling = NullValueHandling.Include;
settings.TypeNameHandling = TypeNameHandling.None;
}
try
{
response.EnsureSuccessStatusCode();
return JsonConvert.DeserializeObject<T>(responseData);
}
catch
{
// Error
Debug.WriteLine(
"Error occurred, the status code is: {0} and Content : {1}",
response.StatusCode, response.Content);
}
return default(T);
}
}
In case of error, the error is handled inside catch body. I want to return these Error informations like StatusCode and Content of response when this happens. But I am unable to make any changes to this generic function. How should handle this.
For this purpose you can use out parameter, which you will pass in your method:
public async static Task<T> GetAsync<T>(string Base_Url,string relative_URL, Utility.UriExtensions.NameValueCollection Params, Utility.UriExtensions.NameValueCollection headers = null, out StatusCode code)
StatusCode statusCode;
GetAsync<Foo>(..., out statusCode);

best overloaded method match for `RestSharp.Deserialize<RootObject>(RestSharp.IRestResponse)' has some invalid arguments

So i am working this project on Xamarin forms, and get the error as in title on
var rootObject = deserial.Deserialize<RootObject>(gameJson);
I am supposed to return the list of games to my app.How can i remove the error?
public async Task<Game[]> GetGamesAsync(){
var client = new RestClient("http://mystore/");
var request = new RestRequest ("api/Games", Method.GET);
request.OnBeforeDeserialization = resp => { resp.ContentType = "application/json"; };
var apiKey = session ["ApiKey"];
var userId = session ["UserId"];
try
{
request.AddHeader ("authenticationkey",apiKey.ToString ());
request.AddHeader ("authenticationid",userId.ToString ());
}
catch{}
IRestResponse response = client.Execute (request);
statusCodeCheck (response);
var gameJson = response.Content;
if (response.StatusCode == HttpStatusCode.OK) {
RestSharp.Deserializers.JsonDeserializer deserial = new RestSharp.Deserializers.JsonDeserializer ();
var rootObject = deserial.Deserialize<RootObject>(gameJson);
return rootObject.games;
}
else if(response.StatusCode == HttpStatusCode.Forbidden){
return null;
}
}
Not sure you are looking for this but I also using Restsharp in portable library and I'm deserializing datacontracts with Json.NET's JsonConvert.DeserializeObject<T>
method. I have not encountered any problem with it yet.
Also another possible solution is that the returned data is wrapped and the main object is not the RootObject.

Nancy OnError will not accept a Response object?

The Nancy documentation seems to say that Pipelines.OnError should return null - as opposed to BeforeResponse which allows both null and a Response object.
All the examples like this one and many code samples here on StackOverflow show a Response being returned in the OnError, just like in the BeforeRequest.
When I attempt to return an HTTPStatus string for the Pipelines.OnError, everything works OK!
But when I attempt to return a Response, I get a compiler error:
Operator '+=' cannot be applied to operands of type 'Nancy.ErrorPipeline' and 'lambda expression'
I'm emulating almost exactly the code in the Nancy example, except for the fact that mine is a TinyIocContainer while the example's is using a StructureMap container and a StructureMap derived bootstrapper
Here's my code:
const string errKey = "My proj error";
const string creationProblem = "Message creation (HTTP-POST)";
const string retrievalProblem = "Message retrieval (HTTP-GET)";
public void Initialize(IPipelines pipelines)
{
string jsonContentType = "application/json";
byte[] jsonFailedCreate = toJsonByteArray(creationProblem);
byte[] jsonFailedRetrieve = toJsonByteArray(retrievalProblem);
Response responseFailedCreate = new Response
{
StatusCode = HttpStatusCode.NotModified,
ContentType = jsonContentType,
Contents = (stream) =>
stream.Write(jsonFailedCreate, 0, jsonFailedCreate.Length)
};
Response responseFailedRetrieve = new Response
{
StatusCode = HttpStatusCode.NotFound,
ContentType = jsonContentType,
Contents = (stream) =>
stream.Write(jsonFailedRetrieve, 0, jsonFailedRetrieve.Length)
};
// POST - error in Create call
pipelines.OnError += (context, exception) =>
{
// POST - error during Create call
if (context.Request.Method == "POST")
return responsefailedCreate;
// GET - error during Retrieve call
else if (context.Request.Method == "GET")
return responseFailedRetrieve;
// All other cases - not supported
else
return HttpStatusCode.InternalServerError;
};
}
private byte[] toJsonByteArray(string plainString)
{
string jsonString = new JObject { { errKey, plainString } }.ToString();
byte[] result = Encoding.UTF8.GetBytes(jsonString);
return result;
}
I had the same problem and I found a nice approach to the problem: http://paulstovell.com/blog/consistent-error-handling-with-nancy.
you should override RequestStartup on the Bootstrapper, here my test code:
protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context)
{
pipelines.OnError.AddItemToEndOfPipeline((ctx, ex) =>
{
DefaultJsonSerializer serializer = new DefaultJsonSerializer();
Response error = new JsonResponse(ex.Message,serializer);
error.StatusCode = HttpStatusCode.InternalServerError;
return error;
});
base.RequestStartup(container, pipelines, context);
}