You need to sign in to do that
Don't have an account?
Testing an Apex Class with @Future - Help Needed
Ok gentlemen - I need some assistance.
I'm a relative newbie to this although I making decent progress punctuated with a a few hours here of despair.
I currently have a custom object being part populated through the API - I then have written the following class which goes away to an external API and pulls back some more fields completed the data - and it works well. But at when I went live with this I wasn't too up on test methods. As such, the class has not got any and thankfully due to the pre-installed packages from Salesforce I am just hovering above 75% through pure luck.
As such, I have this in production and luckily working with triggers but I now need to update the test method or I can't deploy some updated functionality - if anyone have used the deployment functionality you will know what I mean.
Here's the class ignore the test at the bottom - that was purely to get trigger above 0%. My code coverage on the class for this is 0%.
Can you give me some pointers on the following code? Now I've seen the apex instructions on breaking your callouts in 3 sections but I can't seem to make that fit.
Any thoughts - any suggestions welcome... like I said currently at 0%....
public class IncidentCouncilUpdater { //Future annotation to mark the method as async. @Future(callout=true) public static void updateIncident(ID IncidentID) { Incident__c myCoordinates = [SELECT latitude__c, longitude__c FROM Incident__c WHERE Id = :IncidentID]; System.debug('myCoordinates: ' + myCoordinates); String myLat = myCoordinates.latitude__c; String myLong = myCoordinates.longitude__c; System.debug('myLat: ' + myLat); System.debug('myLong: ' + myLong); //Create end point from fields in the database String StrEndPoint = 'http://www.uk-postcodes.com/latlng/'+myLat+','+myLong+'.xml'; System.debug('StrEndPoint: ' + StrEndPoint); //construct an HTTP request HttpRequest req = new HttpRequest(); req.setHeader('Content-Type', 'text/xml'); //req.setEndpoint('http://www.uk-postcodes.com/latlng/53.24354,-2.34567.xml'); req.setEndpoint(strEndPoint); req.setMethod('GET'); req.setTimeout(60000); //send the request Http http = new Http(); HttpResponse res = http.send(req); // Log the XML content Dom.Document doc = res.getBodyDocument(); // print out specific elements by finding the node address dom.XmlNode location = doc.getRootElement() .getChildElement('administrative',null) .getChildElement('district',null); System.debug('location: ' + location); // print out specific elements by finding the node address dom.XmlNode location2 = doc.getRootElement(); System.debug('location2: ' + location2); // gets the content from the XML String district_title; String postcode; district_title = location.getChildElement('title', null).getText(); postcode = location2.getChildElement('postcode', null).getText(); System.debug('district_title: ' + district_title); System.debug('postcode: ' + postcode); //update Incident Incident__c Inc = new Incident__c(Id=IncidentID); Inc.Council_Name_Text__c = district_title; Inc.Incident_Postcode__c = postcode; update Inc; } static testMethod void testIncidentCouncilUpdater(){ Incident__c Inc1 = new Incident__c(name='Other', latitude__c='53.54489', longitude__c='-2.44467'); return inc1.id; String StrEndPoint = 'http://www.uk-postcodes.com/latlng/53.54489,-2.44467.xml'; HttpRequest req = new HttpRequest(); req.setEndpoint(strEndPoint); req.setMethod('GET'); req.setTimeout(60000); Inc1.Council_Name_Text__c = 'Salford City Council'; Inc1.Incident_Postcode__c = 'M30 9QU'; update Inc1; } }
Here's the trigger...
trigger XMLUpdater on Incident__c (after insert) { System.debug('Making future call to update account'); for (Incident__c Inc : Trigger.New) { //Call future method to update account //with data from external server. //This is a async calls, it returns right away, after //enqueuing the request. IncidentCouncilUpdater.updateIncident(Inc.Id); } }
I rewrote your class to allow you to get a better test code coverage. I tested in my Developer Edition and got 94% test code coverage. I couldn't test 4 lines and that is due to the fact that you cannot call a webservice in test methods, so as the links state you have to simulate it.
Some classes are defined with the virtual keyword because I usually overwrite them in the test code coverage, but your code didn't need it, but maybe will in the future.
The test code for the above is below. I wrote three test methods because I like to breakup some of the testing so I can see what is happening in the debug logs better. The last two are basically going to fail because you cannot call webservices in test methods, but I use them to test the lines in the future call where the isApexTest logic is used.
As stated above, I tested this in my Developers account and it works fine. I actually copied your trigger and created your Incident object to test. When I created a new incident record, the trigger calls the future call which retrieves the data from the webservice and then updates the newly created Incident record.
Hope this helps!
All Answers
The first issue is that you cannot execute a 3rd party callout inside test code, but if you break the code into the three parts like you stated then you will be able to get over 75% coverage. I actually broke my code into more than 4 parts because I am doing a lot of parsing and other things.
I gained most of my knowledge to get my Apex code to pass test code coverage by using thethree articles below. I am currently at 96% code coverage for my callout.
Apex Web Services and Callouts
Virtual Callout Testing
Testing HTTP Callouts
I ended up using the logic in 'Virtual Callout Testing' and it works great. Once you understand the above, you should be able to write the test code without a problem. Plus, you will learn a lot about HTTP Callouts and how Apex handles them.
Hope this helps
Thanks Smills - appreciate the links.
I decided to rewrite the class using the boolean to state whether it's a test or not and I've just about managed that. It's still working anyway.
However, I've just read the comments on that blog post and because I am using the @future callout it appears that I cannot use this approach!!!
Has anyone got any other thoughts in relation to the @future part of the code?
That would be a big help smills.
As I said I have rewritten the class below to allow for the boolena test method as highllighted in those links you said.
I am assuming that I will need to use the
Test.startTest();
@Future Callout
Test.stopTest();
At the beginning to allow for the testing of the callout. Here's the code as it stands....
I rewrote your class to allow you to get a better test code coverage. I tested in my Developer Edition and got 94% test code coverage. I couldn't test 4 lines and that is due to the fact that you cannot call a webservice in test methods, so as the links state you have to simulate it.
Some classes are defined with the virtual keyword because I usually overwrite them in the test code coverage, but your code didn't need it, but maybe will in the future.
The test code for the above is below. I wrote three test methods because I like to breakup some of the testing so I can see what is happening in the debug logs better. The last two are basically going to fail because you cannot call webservices in test methods, but I use them to test the lines in the future call where the isApexTest logic is used.
As stated above, I tested this in my Developers account and it works fine. I actually copied your trigger and created your Incident object to test. When I created a new incident record, the trigger calls the future call which retrieves the data from the webservice and then updates the newly created Incident record.
Hope this helps!
:smileysurprised:
Smills - that is way beyond the call of duty!!!!!!
I cannot thank you enough for the time and effort you have put in here, it's extremely impressive. I will get this tested ASAP although I'm sure it looks good.
I'll get this click fixed as soon as I get it into test.
94%!!!!
Unbelievable - that structure should help me immensely with my next projects, Smills!!!
Happy to have helped! I am still learning new things everyday!
Like I stated in my first email, I gained the knowledge from the articles linked above and tailored them for my project. The main article I used was Virtual Callout Testing. So alot of credit has to go to Simon Fell and Dave Carroll for posting that guide. Also, Scott Hammetter's guide in Testing HTTP Callouts gave me insight on how to bypass items with the isApexTest variable that couldn't be tested.
So thank them also!