You need to sign in to do that
Don't have an account?
Batch Apex Test Coverage Problem
I have a simple Batch Apex class called from a trigger on the User object that updates a field on the Accounts owned by the User if a corresponding field is updated on the User.
All's working well as regards functionality, but the test method does not seem to be calling the Execute.
The trigger:
trigger au_User on User (after update) {
List<id> usersToUpdate = new List<id>();
//Loop through the trigger set for (integer iterator=0; iterator<trigger.new.size(); iterator++){
//Add the Users to collection where the values of the given fields have changed
if(trigger.new[iterator].Super_Sector_Region__c <> trigger.old[iterator].Super_Sector_Region__c){
usersToUpdate.add(trigger.new[iterator].id);
}
}
AccountUpdaterBatch batch = new AccountUpdaterBatch();
batch.usersToUpdate = usersToUpdate;
Database.executeBatch(batch, 20);
}
The batch apex class:
global class AccountUpdaterBatch implements Database.Batchable<SObject>, Database.Stateful{
public List<id> usersToUpdate = new List<id>();
global Database.QueryLocator start(Database.BatchableContext BC){
String query='select a.id, a.Sector_Region__c, a.Owner.Super_Sector_Region__c'
+' from Account a where a.OwnerId IN :usersToUpdate';
return Database.getQueryLocator(query);
}
global void execute(Database.BatchableContext BC, List<sObject> scope){
List<Account> accns = new List<Account>();
for(sobject s : scope){
Account a = (Account)s;
if(a.Sector_Region__c != a.Owner.Super_Sector_Region__c)
a.Sector_Region__c = a.Owner.Super_Sector_Region__c;
accns.add(a);
}
update accns;
system.debug('####### Number of accounts updated'+accns.size());
}
global void finish(Database.BatchableContext BC){ //Do nothing }
}
The test method:
static testMethod void testUserTrigger(){
User currentUser = [Select u.Super_Sector_Region__c, u.Id From User u Where u.Id =: UserInfo.getUserID()];
List<Account> testAccs = new List<Account>();
for(integer i=0;i<90;i++){
Account testAccount = new Account(Name = 'TestAccount'+i, OwnerId = currentUser.Id);
testAccs.add(testAccount);
}
insert testAccs;
Test.starttest();
currentUser.Sector_Sales_Team__c = 'Test1';
update currentUser;
AccountUpdaterBatch testBatch = new AccountUpdaterBatch();
testBatch.usersToUpdate = new List<id>{currentUser.Id};
Database.executeBatch(testBatch, 20);
test.stoptest();
}
Finally, the dump from the test log that shows that the start method was called, but the execute wasn't:
20100110162747.501:External entry point: returning Database.QueryLocator from method global Database.QueryLocator
start(Database.BatchableContext) in 0 ms
20100110162747.501:Class.AccountUpdaterBatch.start: line 35, column 10: SOQL locator query with 93 rows finished in 122 ms
Cumulative resource usage:
..
..
..
..
20100110162738.641:Class.TestAccountTrigger.testUserTrigger: line 123, column 3: returning from end of method global static void stopTest() in 5760 ms
20100110162738.641:External entry point: returning from end of method static testMethod void testUserTrigger() in 9065 ms
Can someone please guide me as to what I'm missing or doing wrong?
Thanks,
-Manu
Bob, thanks for all the great help!
Apparently, it was the other code where the update was being called. I removed my code and put a startTest() just before the last code that was updating a user and bam!, it worked! You were right, the trigger gets called so I don't have to call the apex separately!
Good points about the batch of 20. Not sure why the query was failing for you!!
But I'm so relieved. Even so, there really needs to be better debug information in the logs to figure these kind of issues. I know there's quite a lot of changes to logging coming in Spring'10. Hopefully, it'll also help in these kind of scenarios.
Cheers and again thank you for all the help,
-Manu
All Answers
It might help to get more output out of the testmethod, which at least should help track the problem down further.
Try something like this:
ID batchID = Database.executeBatch(testBatch, 20); Test.stopTest(); AsyncApexJob a = [Select Id, Status, NumberOfErrors, JobItemsProcessed, TotalJobItems, CreatedBy.Email from AsyncApexJob where Id = :batchId]; System.debug('\n\nFinal results are: '+a); System.AssertEquals('Completed', a.status); System.AssertEquals(0, a.NumberOfErrors);
At least this way it throws an exception if the batch fails to run properly, and you can tell how many records it was told to run with. But good luck, debugging batch apex was one of my least fun experiences on force.com.
Will your test code not end up calling the executeBatch method twice - you are updating the user in your test method, which will cause it to execute as part of the trigger, and you are then instantiating a new AccountUpdaterBatch and executing it again.
According to the docs, the testing framework allows developers to test one execution of the executeBatch method. Could this be the problem? Somehow your second call takes out the test?
fsd, thanks for that. I tried and it sure does tell that its failing but not where or why! Here's what I get:
Final results are: AsyncApexJob:{Status=Failed, NumberOfErrors=1, CreatedById=005R0000000UwFaIAK, Id=707R0000000UOTMIA4, TotalJobItems=0, JobItemsProcessed=1} System.Exception: Assertion Failed: Expected: Completed, Actual: Failed
Bob, thanks for replying. It could well be the reason, but if it is then how do you reckon that I can get the test coverage required? The docs clearly say that the way to test it is to call the execute explicitly so the trigger call shouldn't work and it should only work when I'm creating an instance of the class and then calling executeBatch.
I even disabled the trigger and ran it but still gives the same failure as above.
I seem to be stuck with this code in sandbox with no way of moving it to production! :-(
Do let me know please if you have any ideas how I could just get the coverage done.
Thanks again,
-Manu
I've read and re-read the docs a few times - as is often the case they can be confusing:
The docs say, "to test an Apex class implementing the Database.Batchable interface, you must simulate a batch job by calling each method implemented by the interface directly".
I reckon that this means to test the class in isolation - i.e. if you have some batch apex that will be executed on demand. As you are calling via a trigger, this will happen for you. I can't see there's any difference between having an executeBatch in the trigger and inside the test class.
Also, if you take the docs literally, you should call the start and finish methods too!
Anyway, presumably you need to include this in your trigger when deploying, so that's what needs to work.
What are the types of super_sector_region__c and sector_region__c?
You're right about the docs being confusing!
The start() is getting called for sure implicitly by the trigger as well as when I call execute. One thing I can think that could be causing the error is the limit on the query, but then I'm not updating over 200 records in my tests so it shouldn't be the case.
super_sector_region__c is a text field and sector_region__c is a picklist.
There's another piece of test code that updates users before my test method is called. From what you say, it could be that the batch gets called repeatedly and thus fails. But the dilemma there is that it still failed when I disabled the trigger. And it also failed when I wrote this in a separate test class and just ran tests on only that class - in this I tried to only update the user and not call execute but again, it just called start and not execute!
Getting a bit closer - I've added your classes to my dev environment.
Firstly, although you aren't updating more than 200 records, you are updating 90 odd in batches of 20. This would cause your execute method to fire 5 times, which isn't allowed - it can only fire ones. If you want to limit the batches to 20, you'll need to ensure your query returns less than 20 records.
Secondly, it doesn't like your query and I've no idea why. If I change the query to just retrieve the id of the account (rather than the various custom fields) it works fine.
Bob, thanks for all the great help!
Apparently, it was the other code where the update was being called. I removed my code and put a startTest() just before the last code that was updating a user and bam!, it worked! You were right, the trigger gets called so I don't have to call the apex separately!
Good points about the batch of 20. Not sure why the query was failing for you!!
But I'm so relieved. Even so, there really needs to be better debug information in the logs to figure these kind of issues. I know there's quite a lot of changes to logging coming in Spring'10. Hopefully, it'll also help in these kind of scenarios.
Cheers and again thank you for all the help,
-Manu
Cool. I second your hope for better logging. If (for example) the query fails when run in batch mode, there's absolutely no feedback about this, other than the fact that the Apex failed!!
Glad you got there in the end.
Going thru learning curve of testing Batch Apex, I read this thread. Just to be sure I'm not loosing my [alledged] mind, please see if I've got this right:
As the testMethod is written now, when
currentUser.Sector_Sales_Team__c = 'Test1';
is encountered, won't the system request that that field be included in the select statement? And, if it were changed to be so, wouldn't the trigger NOT put the currentUser into usersToUpdate since it's testing for a difference on Super_Sector_Region__c?
So, I think it's probably correct to think that the line should have been:
currentUser.Super_Sector_Region__c = 'Test1';
Right?
Hi Bob and Manu,
Thanks for your valuable discussions on this issue.
I have also called batch class from trigger on custom object but while writting the test class i was trying to call execute() method in test class rather it was not required if your trigger is already calling it.
I have removed that call and wrote update statement between Test.StartTest(); and Test.StopTest() and it worked nicely with good coverage.
Thank you very much to both of you for resolving this issue.
Amit