Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Never calling the stub #37

Closed
alvarezloaiciga opened this issue Oct 10, 2013 · 30 comments
Closed

Never calling the stub #37

alvarezloaiciga opened this issue Oct 10, 2013 · 30 comments

Comments

@alvarezloaiciga
Copy link

Hi, I tried to implement stubs on xcode 5 with afnetworking 2.0 and it is not working. My current version of OHHTTPStubs pod is 3.0.0

  [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
    return YES;
  }
     withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) {
                        return [[OHHTTPStubsResponse alloc] init];
                      }];

I am actually using an AFHTTPSessionManager singleton configured:

+ (instancetype)sharedInstance
{
  static APTHttpClient *_sharedClient = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    _sharedClient = [[APTHttpClient alloc] initWithBaseURL:[NSURL URLWithString:APRProductionServer] sessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
  });

  return _sharedClient;
}

This is the method I am actually calling after the stub:

- (void)getContacts
{
  [self GET:@"/contacts"
 parameters:nil
    success:^(NSURLSessionDataTask *task, id responseObject) {
      NSLog(@"%@",responseObject);
    } failure:^(NSURLSessionDataTask *task, NSError *error) {
      NSLog(@"Error: %@", error.userInfo);
    }];
}

Is there something I am doing wrong?

@AliSoftware
Copy link
Owner

This is due to the way the new NSURLSession class works. It was fixed since 2.4.0 already.
See the CHANGELOG here.

Please upgrade to at least 2.4.0 or even better to 3.0.0 😉

@alvarezloaiciga
Copy link
Author

Yeah sorry, I am currently using 3.0.0. Just updated the issue up there. Seems like the issue is with the singleton, is it required to call setEnable:ForSessionConfiguration: before creating the sharedInstance?

@alvarezloaiciga
Copy link
Author

According to what you have in the documentation:

a NSURLSession created using a NSURLSessionConfiguration and [NSURLSession sessionWithConfiguration:] (thanks
to method swizzling that insert the private protocol used by OHHTTPStubs into the protocolClasses of 
[NSURLSessionConfiguration defaultSessionConfiguration] and [NSURLSessionConfiguration ephemeralSessionConfiguration] automagically)

The line in your test example:

[OHHTTPStubs setEnabled:YES forSessionConfiguration:sessionConfig];

Shouldn't be there if its enabled by default. Is there something special with NSURLSessionConfiguration?

@AliSoftware
Copy link
Owner

No more that what I explained in the README.

Yes there is something special with NSURLSessionConfiguration because NSURLSession objects created with an NSURLSessionConfiguration uses NSURLProtocol classes declared in the protocolClasses property of the configuration, contrary to NSURLConnection or the special case of [NSURLSession sharedSession] which use NSURLProtocol classes registered via +[NSURLProtocol registerClass:].

But you shouldn't need to call [OHHTTPStubs setEnabled:YES forSessionConfiguration:sessionConfig] explicitly yourself after creating [NSURLSessionConfiguration defaultConfiguration] because I installed method swizzling that takes care of that for you, so that [NSURLSessionConfiguration defaultConfiguration] returns the defaultConfiguration with the protocolClasses already including OHHTTPStubs' private NSURLProtocol.

Note: Be sure anyway that you added the -ObjC flag to the OTHER_LINKER_FLAGS of your project (which is not related to OHHTTPStubs itself, but must be set everytime you link a project with an Objective-C static library)

@alvarezloaiciga
Copy link
Author

Yep, that's correct. I am getting this array for the protocol classes:

(
    OHHTTPStubsProtocol,
    OHHTTPStubsProtocol
)

But it is not doing the stub correctly, what should I print for you to know about the problem? Can we do hangout or something?

@AliSoftware
Copy link
Owner

One thing I didn't realize, you return a [[OHHTTPStubsResponse alloc] init] instead of some [OHHTTPStubsResponse responseWith…]… it should do anything (like if [… responseWithData:nil]) but that's still a strange and undocumented use of my lib.

Anyway, can you put some breakpoints in both your +sharedInstance, the +load method of NSURLSessionConfiguration+OHHTTPStubs that does the swizzling, and the methods like canInitWithRequest: of my private protocol, to see if it enters each method and in which order?

@alvarezloaiciga
Copy link
Author

Everything correct but this line is returning nil so the condition is return false

[OHHTTPStubs.sharedInstance firstStubPassingTestForRequest:request]

I think I found the issue, is in the singleton, when it adds the stub to the list it has this object as self

<OHHTTPStubs: 0xa0df3f0>

But when it is accesses sharedInstance in

[OHHTTPStubs.sharedInstance firstStubPassingTestForRequest:request]

Its object id is:

<OHHTTPStubs: 0xa2bceb0>

@AliSoftware
Copy link
Owner

Everything correct but this line is returning nil so the condition is return false

[OHHTTPStubs.sharedInstance firstStubPassingTestForRequest:request]


So that means that your code to stubRequestsPassingTest: withStubResponse: didn't get called before your request has been sent. Or that you request to pass the test passed as the first parameter, but as in your example your test always return YES (which is not really a good practice, but still) this can't be the reason.

Verify that [OHHTTPStubs allStubs] returns the list of stubs matching the one you created. (You may give names to your stubs as explained in the README to identify them easily)

@AliSoftware
Copy link
Owner

I think I found the issue, is in the singleton, when it adds the stub to the list it has this object as self

<OHHTTPStubs: 0xa0df3f0>

But when it is accesses sharedInstance in

[OHHTTPStubs.sharedInstance firstStubPassingTestForRequest:request]

Its object id is:

<OHHTTPStubs: 0xa2bceb0>

Can you be more specific? I quite didn't understand your statement here ;)

@AliSoftware
Copy link
Owner

The line in your test example:

[OHHTTPStubs setEnabled:YES forSessionConfiguration:sessionConfig];

Shouldn't be there if its enabled by default. Is there something special with NSURLSessionConfiguration?

I don't know where you see this line in my test example?! I call this method nowhere in my Test Suites, indeed because it is not needed since I provided the swizzling to do it for you automagically.

Are you sure you are using the right version of OHHTTPStubs?!

@alvarezloaiciga
Copy link
Author

Yes, just removed that the +setEnable and still the same. So this is what I got from the debugging:

+(id<OHHTTPStubsDescriptor>)stubRequestsPassingTest:(OHHTTPStubsTestBlock)testBlock
                                   withStubResponse:(OHHTTPStubsResponseBlock)responseBlock
{
    OHHTTPStubsDescriptor* stub = [OHHTTPStubsDescriptor stubDescriptorWithTestBlock:testBlock
                                                                       responseBlock:responseBlock];
    [OHHTTPStubs.sharedInstance addStub:stub];
    NSLog(@"%@", OHHTTPStubs.sharedInstance) -> <OHHTTPStubs: 0xa0df3f0>
    return stub;
}


+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
    NSLog(@"%@", OHHTTPStubs.sharedInstance) -> <OHHTTPStubs: 0xa2bceb0>
    return ([OHHTTPStubs.sharedInstance firstStubPassingTestForRequest:request] != nil);
}

So its adding the stub to object A, but it is looking for that stub in object B.

@AliSoftware
Copy link
Owner

Ok I understand your analyze but I don't get how come the sharedInstance can point to a different object (despite the dispatch_once call). What if you put a breakpoint in sharedInstance, and especially in that part of the sharedInstance method where it affects its value, to check why it could be run twice and in which context (call stack)?! So we can understand at what point it could have been reaffected?!

Thx again for the investigation

@alvarezloaiciga
Copy link
Author

Ok, got it. The problem is on concurrency.

It's actually getting a different sharedInstance when I am doing the waitForAsyncOperationWithTimeout. I took the parent class that has this method from your examples too.

Is there a different way to make it wait for the block calls?

@AliSoftware
Copy link
Owner

waitForAsyncOperationWithTimeout is a method, not a class.

I can't understand why the problem can be on concurrency, whereas dispatch_once is especially designed to be thread-safe, and is the official GCD way to make sure a resource or code block is executed only once, even if accessed from multiple threads. So this sounds really uncanny that you could enter into this dispatch_once block twice and make two different sharedInstances.

Can you post your complete code somewhere? I feel like only having pieces with your comments

@alvarezloaiciga
Copy link
Author

https://gist.github.com/alvarezloaiciga/6925055

I gave it a good range of timeout but it should not affect.

@AliSoftware
Copy link
Owner

Ok I'll test that.

By the way, why did you use a global variables (which by definition is not thread-safe) for your onceToken and sharedInstance, instead of a static inside your sharedInstance method like we usually do? It really feels unsafe to me to give the opportunity to change the _sharedInstance variable from anywhere

@alvarezloaiciga
Copy link
Author

Just to be able to reset the shareInstance on the tearDown

And thanks so much for your help!

@AliSoftware
Copy link
Owner

Which doesn't make any sense, as it won't get reinitialized even if you got another test case, as your onceToken would already have been used and there's no way to reset it, right?

@alvarezloaiciga
Copy link
Author

There is a way to reset it but I am not using it in this case. (I am actually reseting other singletons). You can move it to the method declaration to see if thats the issue.

@AliSoftware
Copy link
Owner

I ran you Unit Test (I guessed the superclass of your APTHttpClient was AFHTTPSessionManager?) on my machine, only went into +[OHHTTPStubs sharedInstance]'s dispatch_once block once as expected, the test passed without failing, no issue here.

@AliSoftware
Copy link
Owner

I even added a STAssertEqualObjects(response, expectedResponseDict, @"Unexpected response"); at the end of your test, and if passes without a problem.
Xcode5, SDK7, test run on "iPhone Retina (4-inch)" and "iPhone Retina (4-inch 64-bit)" simulators, running iOS7.

Still not understanding your issue.

@alvarezloaiciga
Copy link
Author

can we have a quick hangout?

@AliSoftware
Copy link
Owner

Not tonite, I'm quite doing too much things at once already this evening and late for everything ^^ But maybe if you still have the issue over the weekend.

@alvarezloaiciga
Copy link
Author

great, are you running the code in xcode 5? Cuz' you are using STAssert rather than XCT

@AliSoftware
Copy link
Owner

As said above, Xcode5 and SDK7 of course. (I couldn't even build AFNetworking 2.0 If I was not using Xcode5…)

But I'm still using SenTestingKit as I haven't ported my tests to XCTest yet and my AsyncSenTestCase class is still a subclass of SenTestCase, and all my already written Unit Tests are using SenTestCase. (I know that only takes 2 minutes with the Xcode assistant, but as Travis-CI is still building with Xcode4 I still have to postpone the migration and am forced to keep using SenTestKit until then — but that shouldn't be an issue as SenTestKit is still supported by Xcode5).

@alvarezloaiciga
Copy link
Author

It is working know, but I needed to remove the pod file and just added the xcode project to the workspace. So it might be related to the pod?

@AliSoftware
Copy link
Owner

Could you try again your original method (the one that didn't work) with the latest version/commit/HEAD, to let me know if it completely fixes your original issue?

Thx!

@AliSoftware
Copy link
Owner

Note: 3.0.1 is out with this fix!

@danpizz
Copy link

danpizz commented Nov 21, 2013

If for some reason you add the static OHHTTPStubs to both your project and your tests bundle, it will give you bad headaches because you'll end up with 2 bundles and 2 OHHTTPStubs singleton instances!

This explains the different instances address despite the dispatch_once call and the fact that the problem was resolved by removing the pod file.

This happens if you mess with pods, adding and removing XCode targets, because pod-generated static libs may get stuck and not removed by pod update or clean.

I think it's better to add some documentation because it's a bad problem to trace down if you don't relize about multiple bundles.

@AliSoftware
Copy link
Owner

Hello again @danpizz

We are struggling with a related issue in #47 and I finally understood all the implications that you explain when you were talking about having two bundles when running Unit Tests.

I in fact did the following test, as you could see in #47:

  • When selecting my Unit Test target ("MyAppTests") in my app's xcodeproj, then open the "Target" dropdown menu in the "General" tab of the test target, we have two options: either selecting the application's target ("MyApp") as the target for those Unit Tests, or selecting "None"
  • If I select "None", OHHTTPStubs is +initialize'd only once, we have only one singleton instance and everything works fine
  • If I select the application "MyApp" in this dropdown menu, when running the Unit Tests, my application's AppDelegate code gets executed (application:didFinishLaunchingWithOptions:) before my Test Bundle is loaded and my Unit Tests are executed. That's what you were talking about when you said that we end up with two bundles: the one for the application, that then loads the test bundle (quite like a "plugin")

So I have two questions that I hope you can help me with:

  • Do you see any case where we would need the "Target" dropdown menu to be set to the app's target ("MyApp") instead of "None"? In other words, would it be an acceptable solution to force users of my lib to set this dropdown to "None" ? I can't see why, when we create a new Xcode projet, this dropdown is set to the App's target by default instead of "None" and in which case is it necessary/useful to set it to the App's target ? Maybe for UI/Application Tests, but not for Functionnal/UnitTests?
  • Do you have any suggestion/idea of a way to protect my code against such case (OHHTTPStubs being loaded by two bundles, the application bundle + the test bundle), especially having some test that check if OHHTTPStubs is already loaded in another bundle and use this one instead (or at least not doing the swizzling in this case)?

Thanks in advance for your feedback

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants