You need to sign in to do that
Don't have an account?
Daniel B Probert
Apex Class test coverage issues.
Hi All,
I'm having an issue getting my code coverage for an Apex class i've written above 0.
here is the apex class:
public class TwilioMMASyncSMS { @future (callout=true) public static void sendSMS(Set<Id> Ids) { String toNumber = ''; String messageBody = ''; for (Monitoring_Visit_Bursary__c mvp: [SELECT Id, SMSBody__c, User_ID_Phone__c FROM Monitoring_Visit_Bursary__c WHERE id IN :Ids]) { if (mvp.User_ID_Phone__c != null) { TwilioRestClient SMSclientmvp = TwilioAPI.getDefaultClient(); toNumber = mvp.User_ID_Phone__c; messageBody = mvp.SMSBody__c; Map<String,String> params = new Map<String,String> { 'To' => toNumber, 'From' => '+14156830489', 'Body' => messageBody }; TwilioSMS sms = SMSclientmvp.getAccount().getSmsMessages().create(params); } } for (School_Termly_Update__c stu: [SELECT Id, SMSBody__c, User_ID_Phone__c FROM School_Termly_Update__c WHERE id IN :Ids]) { if (stu.User_ID_Phone__c != null) { TwilioRestClient SMSclientstu = TwilioAPI.getDefaultClient(); toNumber = stu.User_ID_Phone__c; messageBody = stu.SMSBody__c; Map<String,String> params = new Map<String,String> { 'To' => toNumber, 'From' => '+14156830489', 'Body' => messageBody }; TwilioSMS sms = SMSclientstu.getAccount().getSmsMessages().create(params); } } for (Structure_Status_Report__c ssr: [SELECT Id, SMSBody__c, User_ID_Phone__c FROM Structure_Status_Report__c WHERE id IN :Ids]) { if (ssr.User_ID_Phone__c != null) { TwilioRestClient SMSclientssr = TwilioAPI.getDefaultClient(); toNumber = ssr.User_ID_Phone__c; messageBody = ssr.SMSBody__c; Map<String,String> params = new Map<String,String> { 'To' => toNumber, 'From' => '+14156830489', 'Body' => messageBody }; TwilioSMS sms = SMSclientssr.getAccount().getSmsMessages().create(params); } } for (New_Cama_Review__c ncr: [SELECT Id, SMSBody__c, User_ID_Phone__c FROM New_Cama_Review__c WHERE id IN :Ids]) { if (ncr.User_ID_Phone__c != null) { TwilioRestClient SMSclientncr = TwilioAPI.getDefaultClient(); toNumber = ncr.User_ID_Phone__c; messageBody = ncr.SMSBody__c; Map<String,String> params = new Map<String,String> { 'To' => toNumber, 'From' => '+14156830489', 'Body' => messageBody }; TwilioSMS sms = SMSclientncr.getAccount().getSmsMessages().create(params); } } for (Background_data__c bd: [SELECT Id, SMSBody__c, User_ID_Phone__c FROM Background_data__c WHERE id IN :Ids]) { if (bd.User_ID_Phone__c != null) { TwilioRestClient SMSclientbd = TwilioAPI.getDefaultClient(); toNumber = bd.User_ID_Phone__c; messageBody = bd.SMSBody__c; Map<String,String> params = new Map<String,String> { 'To' => toNumber, 'From' => '+14156830489', 'Body' => messageBody }; TwilioSMS sms = SMSclientbd.getAccount().getSmsMessages().create(params); } } for (New_Grade_10_Girls__c ng10g: [SELECT Id, SMSBody__c, User_ID_Phone__c FROM New_Grade_10_Girls__c WHERE id IN :Ids]) { if (ng10g.User_ID_Phone__c != null) { TwilioRestClient SMSclientng10g = TwilioAPI.getDefaultClient(); toNumber = ng10g.User_ID_Phone__c; messageBody = ng10g.SMSBody__c; Map<String,String> params = new Map<String,String> { 'To' => toNumber, 'From' => '+14156830489', 'Body' => messageBody }; TwilioSMS sms = SMSclientng10g.getAccount().getSmsMessages().create(params); } } for (Monitoring_Visit__c mv: [SELECT Id, SMSBody__c, User_ID_Phone__c FROM Monitoring_Visit__c WHERE id IN :Ids]) { if (mv.User_ID_Phone__c != null) { TwilioRestClient SMSclientmv = TwilioAPI.getDefaultClient(); toNumber = mv.User_ID_Phone__c; messageBody = mv.SMSBody__c; Map<String,String> params = new Map<String,String> { 'To' => toNumber, 'From' => '+14156830489', 'Body' => messageBody }; TwilioSMS sms = SMSclientmv.getAccount().getSmsMessages().create(params); } } for (Camfed_Annual_data__c cad: [SELECT Id, SMSBody__c, User_ID_Phone__c FROM Camfed_Annual_data__c WHERE id IN :Ids]) { if (cad.User_ID_Phone__c != null) { TwilioRestClient SMSclientcad = TwilioAPI.getDefaultClient(); toNumber = cad.User_ID_Phone__c; messageBody = cad.SMSBody__c; Map<String,String> params = new Map<String,String> { 'To' => toNumber, 'From' => '+14156830489', 'Body' => messageBody }; TwilioSMS sms = SMSclientcad.getAccount().getSmsMessages().create(params); } } } }
When writing the test class I figured that I would need to create an example of all the related object records that allow me to get this information (note that SMSBody and User_ID_Phone__c are formula fields).
This is the test class that I have written:
@isTest public class TwilioMMASyncSMSTest { static testMethod void TwilioMMASyncSMS() { String toNumber = ''; String messageBody = ''; // create a country Country__c country = new Country__c(); country.Name = 'test country'; insert country; // create a district District__c district = new District__c(); district.Name = 'test'; district.Country__c = country.id; insert district; // create a school School__c school = new School__c(); school.Name = 'test'; school.District__c = district.id; insert school; // create a contact Contact contact = new Contact(); contact.LastName = 'test'; contact.District__c = district.id; contact.School__c = school.id; contact.Country__c = country.id; insert contact; //create a structure Structure__c structure = new Structure__c(); structure.Country__c = country.id; structure.District__c = district.id; structure.Chair__c = contact.id; structure.School__c = school.id; insert structure; // create a Monitoring Visit to Bursary Monitoring_Visit_Bursary__c mvp = new Monitoring_Visit_Bursary__c(); mvp.Person__c = contact.id; mvp.User_ID__c = 'uk07717417554@gmail.com'; insert mvp; // create school termly update School_Termly_Update__c stu = new School_Termly_Update__c(); stu.School__c = school.id; stu.User_ID__c = 'uk07717417554@gmail.com'; insert stu; // create Structure Status Report Structure_Status_Report__c ssr = new Structure_Status_Report__c(); ssr.Structure__c = structure.id; ssr.User_ID__c = 'uk07717417554@gmail.com'; insert ssr; // create New Cama Review New_Cama_Review__c ncr = new New_Cama_Review__c(); ncr.Country__c = country.id; ncr.District__c = district.id; ncr.Graduation_School_Same_District__c = school.id; ncr.User_ID__c = 'uk07717417554@gmail.com'; insert ncr; // create Background Data Background_data__c bd = New Background_data__c(); bd.Name = 'DB Test'; bd.Structure__c = structure.id; bd.User_ID__c = 'uk07717417554@gmail.com'; insert bd; // create New Grade 10 Girls New_Grade_10_Girls__c ng10g = New New_Grade_10_Girls__c(); ng10g.school__c = school.id; ng10g.User_ID__c = 'uk07717417554@gmail.com'; insert ng10g; // create Monitoring Visit Monitoring_Visit__c mv = New Monitoring_Visit__c(); mv.MSG_Visiting__c = structure.id; mv.Primary_Monitor__c = contact.id; mv.school__c = school.id; mv.tm__c = contact.id; mv.User_ID__c = 'uk07717417554@gmail.com'; insert mv; // create Camfed Annual Data Camfed_Annual_data__c cad = New Camfed_Annual_data__c(); cad.school__c = school.id; cad.User_ID__c = 'uk07717417554@gmail.com'; insert cad; for (Monitoring_Visit_Bursary__c mvpsms: [SELECT Id, SMSBody__c, User_ID_Phone__c FROM Monitoring_Visit_Bursary__c WHERE id = :mv.id]) { if (mvpsms.User_ID_Phone__c != null) { toNumber = mvpsms.User_ID_Phone__c; messageBody = mvpsms.SMSBody__c; Map<String,String> params = new Map<String,String> { 'From' => '+14156830489', 'To' => toNumber, 'Body' => messageBody }; } } for (School_Termly_Update__c stusms: [SELECT Id, SMSBody__c, User_ID_Phone__c FROM School_Termly_Update__c WHERE id = :stu.id]) { if (stusms.User_ID_Phone__c != null) { toNumber = stusms.User_ID_Phone__c; messageBody = stusms.SMSBody__c; Map<String,String> params = new Map<String,String> { 'To' => toNumber, 'From' => '+14156830489', 'Body' => messageBody }; } } for (Structure_Status_Report__c ssrsms: [SELECT Id, SMSBody__c, User_ID_Phone__c FROM Structure_Status_Report__c WHERE id = :ssr.id]) { if (ssrsms.User_ID_Phone__c != null) { toNumber = ssrsms.User_ID_Phone__c; messageBody = ssrsms.SMSBody__c; Map<String,String> params = new Map<String,String> { 'To' => toNumber, 'From' => '+14156830489', 'Body' => messageBody }; } } for (New_Cama_Review__c ncrsms: [SELECT Id, SMSBody__c, User_ID_Phone__c FROM New_Cama_Review__c WHERE id = :ncr.id]) { if (ncrsms.User_ID_Phone__c != null) { toNumber = ncrsms.User_ID_Phone__c; messageBody = ncrsms.SMSBody__c; Map<String,String> params = new Map<String,String> { 'To' => toNumber, 'From' => '+14156830489', 'Body' => messageBody }; } } for (Background_data__c bdsms: [SELECT Id, SMSBody__c, User_ID_Phone__c FROM Background_data__c WHERE id = :bd.id]) { if (bdsms.User_ID_Phone__c != null) { toNumber = bdsms.User_ID_Phone__c; messageBody = bdsms.SMSBody__c; Map<String,String> params = new Map<String,String> { 'To' => toNumber, 'From' => '+14156830489', 'Body' => messageBody }; } } for (New_Grade_10_Girls__c ng10gsms: [SELECT Id, SMSBody__c, User_ID_Phone__c FROM New_Grade_10_Girls__c WHERE id = :ng10g.id]) { if (ng10gsms.User_ID_Phone__c != null) { toNumber = ng10gsms.User_ID_Phone__c; messageBody = ng10gsms.SMSBody__c; Map<String,String> params = new Map<String,String> { 'To' => toNumber, 'From' => '+14156830489', 'Body' => messageBody }; } } for (Monitoring_Visit__c mvsms: [SELECT Id, SMSBody__c, User_ID_Phone__c FROM Monitoring_Visit__c WHERE id = :mv.id]) { if (mvsms.User_ID_Phone__c != null) { toNumber = mvsms.User_ID_Phone__c; messageBody = mvsms.SMSBody__c; Map<String,String> params = new Map<String,String> { 'To' => toNumber, 'From' => '+14156830489', 'Body' => messageBody }; } } for (Camfed_Annual_data__c cadsms: [SELECT Id, SMSBody__c, User_ID_Phone__c FROM Camfed_Annual_data__c WHERE id = :cad.id]) { if (cadsms.User_ID_Phone__c != null) { toNumber = cadsms.User_ID_Phone__c; messageBody = cadsms.SMSBody__c; Map<String,String> params = new Map<String,String> { 'To' => toNumber, 'From' => '+14156830489', 'Body' => messageBody }; } } } }
It deploys fine and validates fine just doesn't aid my code coverage for my main class.
What am I missing here?
Thanks
Dan
Yes, the placement is correct
I'm kind of surprised the callout is being done in a testmethod as callouts can't be rolled back like SFDC DML. I suspect the problem is in the setup to the callout
But, if you don't want to call Twilio at all in the testmethod, then you can do something easy like this:
If you need to actually inspect the results of the Twilio service, then you can build an interface that is by default the production interface and is switched in to be different by the testmethod. I've done this when testing inbound email handlers. The approach below was supplanted by the test.setMock solution I offered earlier that came out in a later SFDC version; below is the SFDC solution prior to Test.setMock
All Answers
Dan --
your main class seems to use an @future annottaion. Per the SFDC doc, you need to wrap your testmethod in Test.startTest() - Test.stopTest()
So, no stopTest() means the test context never invokes the @future method. Furthermore, your @future method is doing callouts, you may want to mock out the response as SFDC won't actually do the callout. See
http://www.salesforce.com/us/developer/docs/apexcode/index_Left.htm#StartTopic=Content/apex_classes_restful_http_testing_static.htm?SearchType=Stem
thanks eric thats good to know although I'm still having a major headache deploying my test apex and so now not even sure if i'm approaching it correctly.
this is a simplified apex class
i then call this from my object Monitoring_Visit_Bursary__c via this trigger:
and this works perfectly and I can deploy to my production - however it shows 0% coverage which obviously I want to resolve.
In order for me to create a record within my test class to invoke the trigger I need to create a record on Monitoring_Visit_Bursary object, in order for me to do this I need to create a test country/district/school & contact as they are all required fields on the MVB object.
Including what you have pointed me toward this is the test class for the above:
when i go to deploy my testclass I receive an error:
SMSMVPTest.sendSMS() Class 425
Failure Message: "TwilioRestException: Script-thrown exception", Failure Stack Trace: "Class.TwilioRestClient: line 425, column 1 Class.TwilioResource.ListResource: line 440, column 1 Class.TwilioSmsList: line 74, column 1 Class.SMSMVP: line 18, column 1"
i'm totally stumped as to how to go about the mock callout? as i'm guessing that the error i'm getting there is related to not actually doing the call out?
cheers for your response.
dan
Dan
First of all, your testmethod can be more compact as shown here:
As for your specific error, it looks more like a setup issue with Twilio (and I'm not familiar with this package). Your testmethod doesn't instantiate smsBody__c in the MVP object. Could that be it?
great thanks, nice to learn how to simplify my test code.
the reason SMSBody and User_ID_Phone aren't in the test is because there are a formula that feed off the User_ID field.
Basically they check for the first 2 charicters and fill in the test based on that information - so my thought was that just so long as I ensure User_ID is loaded then my trigger will fire which I believe it is.
Hence me being stumped by the problem it must be a twilio issue i'll see if I can find anything in their community that may point me in the right direction - it's annoying because the code itself works perfectly :(
cheers for your help.
dan
Note that your testmethod is @isTest annotated which means no org data is used for the execution. It is possible that the Twilio code requires org data so as a lark, try @isTest(seeAlldata=true)
Dan
another idea - this is what my code does with callouts and testmethods
1. Add this class:
2. And your testmethod does the following:
which must be after the Test.startTest() and before the Test.stoptest()
This gives the apex test context something to invoke when it does a callout; everything is dummy here as it assumes you don;t care about testing a mock response from Twilio
i've just tried this but getting the same error when deploying the testclass. i was able to deploy the calloutmock without an issue.
just to confirm this is what you mean by where to put the line:
this what my test class now looks like:
someone on the twilio IRC channel said it's twilio giving an error response so all i've got to figure out if how to stop it from doing the twilio callout and it should be fine :)
Yes, the placement is correct
I'm kind of surprised the callout is being done in a testmethod as callouts can't be rolled back like SFDC DML. I suspect the problem is in the setup to the callout
But, if you don't want to call Twilio at all in the testmethod, then you can do something easy like this:
If you need to actually inspect the results of the Twilio service, then you can build an interface that is by default the production interface and is switched in to be different by the testmethod. I've done this when testing inbound email handlers. The approach below was supplanted by the test.setMock solution I offered earlier that came out in a later SFDC version; below is the SFDC solution prior to Test.setMock
eric i can't thank you enough i 100% owe you a beer for this one.
the code coverage is 88% as well which is good enough for me :)
thank you so much for keep responding and helping me get through this one learnt alot.
cheers
dan