function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Thomas OrozcoThomas Orozco 

Testing Webservice Callouts with Winter '13 Test.setMock fails because of Test Data creation.

Hello everyone,

 

We are trying to use the new API Salesforce made available during Winter '13: Test.setMock.

We followed the documentation, but encountered the following issue that we were unable to resolve: a System.CalloutException, complaining that we had "uncommitted work pending".

 

Of course we do, our testmethod starts by setting up some Test Data that's necessary for the test to work, and that includes inserting and updating data.

In an actual execution, there would be no data creation, as the data would already be present. Actually doing the callout doesn't raise the Uncomitted work pending exception, so the Test Data really is at fault.

 

We did use Test.startTest() and Test.stopTest() to differentiate Test Data creation from Test Execution but this (as expected) didn't work.

We also tried some hacks, including the System.runAs(new User( Id = UserInfo.getUserId()){} that had, in the past, allowed us to circumvent "Mixed DML operations". This didn't work either.

 

We considered using the Test.loadData method, but we're inserting related objects and couldn't figure out a clean enough way to make it work (maybe we should try using ExternalID's?).

 

 

For the moment, our workaround is to modify the class generated from the WSDL, adding a if (Test.isRunningTest()) {} else {}, but that means we have to modify the autogenerated code, which is suboptimal.

 

 

Did anyone try to use this new API and run into the same issue? How did you solve it?

Best Answer chosen by Admin (Salesforce Developers) 
BBeairdBBeaird

Good news! I figured out how to get things to deploy. On all my classes that use mock webservices, I made sure and enclosed the test within Test.StartTest() and Test.StopTest(). After I did that, the deploy tests passed!

 

There's still a big question around why this is needed now - previously I had these classes in production without the start/stop, and they worked fine. It's also still a question as to why these same tests pass via "Run All Tests" but fail during the deployment test process. As someone has reported in other threads, it seems like the test for figuring out if DML has occurred before a webservice callout is done is actually happening across apex tests. So basically, it goes like this:

 

1.) A test class runs for a class that doesn't do any webservice calls. It does DML stuff as part of the test

2.) A test class runs for a class that uses a mock class and does a webservice call. Neither test class nor apex class does any DML

3.) An "uncommitted work pending" error is thrown because some DML was done in the first test class before the second test class runs.

 

Since they are two separate test classes, it really seems like that DML check should not be getting triggered in this situation. Anyway, I'm just glad that the start/stop  test method isolates things enough for me to keep moving for now.

All Answers

colemabcolemab

Thomas,

 

I have used this in my USPS component (blog link) and I was able to avoid it by directly creating the object that I wanted w/o inserting it into the DB via DML and then setting the variable in the controller directly to this new object.

 

Of course, I am using a component so I am not 100% sure this would work in a regular apex class as you would be querying for data some where in there.   Perhaps you can run those queries after the call out?

 

Thanks

Thomas OrozcoThomas Orozco

Hi Colemab, and thanks for answering!

 

Your solution is smart and interesting, but  I fear we'll be running into the following problems:

  • The component we're testing is a VFP page that expects to do its queries on its own to have information to do its callout. Maybe we could use Test.isRunningTest, but I'd rather not have to
  • The objects used have relationships, which I believe force us to insert the objects first. But maybe we could assign to Object1__c.Lookup__r. 

 

I'll look into how practical the workarounds necessary to use your solution would be!

Although I would prefer a more supported solutions.

 

Cheers!

 

Thomas

colemabcolemab

I just tried to deploy the component on a different org (that was just updated to winter 13 this weekend) and I am now getting the callout errors - even without making any direct DML (Update, Insert, Upsert) statements.

 

Looking into it . . . . .

colemabcolemab

This may be a known issue - link.

Thomas OrozcoThomas Orozco

Well, in my (our) case, I guess that the error is the "right one", as we indeed have uncomitted data.

Thing is, we don't really have a choice when we're testing, do we?

 

Maybe I'll raise an issue too?

colemabcolemab

Well in your example, you are using DML statements to commit your data to the database.

 

However, in my component I am avoiding these DML statements by just having uncommited updates (i.e. changing the field on an object and then setting that object directly in the controller).  I did this in the test system to avoid this exact error. 

 

However, now that Winter 13 is in production the error occurs in production and not in test.

 

This appears to be yet another example of test not behaving like production . . . .

 

So based on this, not even my work around will work in production.

Thomas OrozcoThomas Orozco

Actually, the error message we're getting indicates "Uncomitted work" and suggests Committing or doing a Rollback.

 

Unless I'm mistaken, the usual Workflow from Salesforce is that a Database Transaction is opened for every Salesforce code unit, and will be closed (i.e. comitted) at the end of the code execution.

Unless the code execution was a Test, in which case it's Rollback'ed to ensure Test Data isolation.

 

I don't think that instantiating an SObject means uncomitted, I believe that uncommitted is purely a Database concept.

 

I'll try to figure out exactly which statements create an error and which don't.

 

Cheers,

 

Thomas

colemabcolemab

Running all tests on my code in test enviornment does not throw this error as the deployment to production does.   Something is clearly different between production and test in Winter 13.

Thomas OrozcoThomas Orozco

We are getting the issue in a Sandbox too, so I think we may not have the exact same issue?

 

Thomas 

colemabcolemab

I agree that we have different issues.

 

So that we can see if you can repeat the issue I am having (and determine if that work around would work for you), could you create a test class that creates an object, sets fields on that object but does not commit them via DML and then makes an HTTP call out (mock or otherwise)?

 

Then see if it works in production and in test? Mine works in test (even with run all apex tests) but fails in production.  I am on CS4/NA4 for test/production.

 

If you don't want to write one, you are welcome to try and delpoy my USPS component but it has several classes.

 

Thanks

Thomas OrozcoThomas Orozco

I tried deploying your package (checkOnly mode), and the tests passed, 

 

I'm on CS7 for Test, but I can't try a deployment against production now, I'll look into the feasibility.

 

Cheers,

 

Thomas

colemabcolemab

Thomas - thank you for testing.

 

This works w/o issue in test sandbox on CS4 and w/o issue in dev system on NA14 but gives an error trying to deploy to production on NA4.

 

When you get a chance, let me know your results in production (even if validate only).  That way I can know if it is an instance thing or not.

chris_centrachris_centra

i'm facing this issue as well and have not found a workaround.  Someone posted an idea if anyone wants to promote:

 

http://success.salesforce.com/ideaView?id=08730000000jcfAAAQ

 

thanks

chris

colemabcolemab

I submitted an issue with support because my class wasn't creating any test data or updating any fields at all. 

 

After much discussion, this appears to be a known issue in the deployment tool for Winter 13.  Basically the deployment tool isn't running each class as an individual run during predeployment testing and so this error will popup for new classes with call outs that run after test classes that do any DML.   Which is just about every class right?

 

Please note that since the deploy tests are different than the run all tests (for unknown reasons), you will never see this kind of error until you deploy to production.

 

Since the tests on deployment run in alpha order, the workaround from support was to take my new class and name it very high alpha sort like 'aaa' so that it could run before any DML in other classes.

 

Support didn't provide a ETA for a fix and didn't say if this would be listed on the official known issues list.

Daniel BallingerDaniel Ballinger

I just ran into a very similar issue when creating a managed package where some of the test methods used Test.setMock with a class that implements WebServiceMock. See WebServiceMock causing “CalloutException: You have uncommitted work pending” when creating managed package.

 

Basically the tests would pass unless they were run as part of creating the managed package.

 

I'll try creating a test class with an "aaa" prefix to run the mock tests and see if that resolves the issue.

 

Thanks.

dmchengdmcheng

I am getting the "Uncommitted work pending" error as well.  My situation is that i have a trigger calling a future method, which then calls a separate method that makes the callout.  I need to use DML to test the trigger, of course.  In my unit test I don't have any other DML before the DML statement that fires the trigger, but it still fails.

 

The trigger and future method work fine at the UI level, just not in unit tests.

BBeairdBBeaird

It looks like this may have been fixed in Spring '13. I haven't looked closely, but I think a couple of my test classes using webservice mocks that were failing due to needing some test data created are now suddenly passing. Can anyone else confirm?

doubleminusdoubleminus

Having the same issue as well. My tests all pass when run in Eclipse. But when I try to deploy, I get a failure caused by my mock classes not returning XML responses in the tests. I have no real way around this that I can find.

 

And note, these failures are occurring in Spring '13 unfortunately.

BBeairdBBeaird
I thought it was working for me, but it only works when doing "run all test" in a sandbox. As soon as I try and deploy the same code to production, it fails with that error message.
fgwarb_devfgwarb_dev

Same here, was hoping that Spring '13 would fix this but it hasn't (at least for how I'm trying to do things)

BBeairdBBeaird

Good news! I figured out how to get things to deploy. On all my classes that use mock webservices, I made sure and enclosed the test within Test.StartTest() and Test.StopTest(). After I did that, the deploy tests passed!

 

There's still a big question around why this is needed now - previously I had these classes in production without the start/stop, and they worked fine. It's also still a question as to why these same tests pass via "Run All Tests" but fail during the deployment test process. As someone has reported in other threads, it seems like the test for figuring out if DML has occurred before a webservice callout is done is actually happening across apex tests. So basically, it goes like this:

 

1.) A test class runs for a class that doesn't do any webservice calls. It does DML stuff as part of the test

2.) A test class runs for a class that uses a mock class and does a webservice call. Neither test class nor apex class does any DML

3.) An "uncommitted work pending" error is thrown because some DML was done in the first test class before the second test class runs.

 

Since they are two separate test classes, it really seems like that DML check should not be getting triggered in this situation. Anyway, I'm just glad that the start/stop  test method isolates things enough for me to keep moving for now.

This was selected as the best answer
colemabcolemab

I have marked BBeaird's comments about using the start/stop test in your code as the solution because it worked for me.

 

Thank you for sharing this information!

fgwarb_devfgwarb_dev

Worked for me as well.

sunny kapoorsunny kapoor

worked for me as well

crop1645crop1645

Note that the following elaborates the previous solution

 

private static testmethod void myTest() {

//  do my DML stmts setting up test data here - they don't have to be inside the Test.start-stop pair

Test.startTest();
Test.setMock(...)

// do my tests here

Test.stopTest();
}

 

colemabcolemab

In the Summer 13 Release notes, Salesforce now documents the fix that this thread discusses:


 

To perform DML operations in test methods before mock callouts, enclose the portion of your code that performs the callout within Test.startTest and Test.stopTest statements. 

The Test.startTest statement must appear before the Test.setMock statement. Also, the calls to DML operations must not be part of the Test.startTest/Test.stopTest block.

 


 

chris_centrachris_centra
a little late to the party here.  i found that this works fine when my (mock) callout is initiated after a trigger call.  however when it's initiated in a batch process, i'm still seeing the same behavior.  anyone else notice this?  full thread here: https://developer.salesforce.com/forums/#!/feedtype=SINGLE_QUESTION_DETAIL&dc=Apex_Code_Development&criteria=OPENQUESTIONS&id=906F00000009DMlIAM

thanks
chris
NawshineNawshine

Hello yes we got the same issue as @chris_centra. The issue still exist when testng for batch process even when test.startTest and test.stopTest is used.

Any solution for this?

evan1.3924178124535508E12evan1.3924178124535508E12
I am still getting this issue when attempting to deploy code today. I've got my code nicely wrapped in the Test.startTest and the Test.stopTest. This is very frustrating. The unit test runs just fine in sandbox, but won't deploy.

I'm having to resort to removing some of my assertions to allow this to deploy.
TracMikeLTracMikeL
Same problem as above. Does not work when using Batch.
md rafimd rafi
The Test.startTest() and Test.stopTest() really solved my problem.