Giter Club home page Giter Club logo

Comments (9)

raphaeloliveira avatar raphaeloliveira commented on June 19, 2024

+1

from nocilla.

spekke avatar spekke commented on June 19, 2024

I'm having the same problem, but I think the problem is not with Nocilla. It seems like NSURLSession and NSURLProtocol doesn't play well together under a testing environment.

In the example below the method + (BOOL)canInitWithRequest: is never called.

#import <XCTest/XCTest.h>

#define TestNeedsToWaitForBlock() __block BOOL blockFinished = NO
#define BlockFinished() blockFinished = YES
#define WaitForBlock() while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) && !blockFinished)

@interface NSURLProtocolImpl : NSURLProtocol
@end

@implementation NSURLProtocolImpl

+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
    NSLog(@"NSURLProtocolImpl canInitWithRequest: %@", request);
    return NO;
}

@end


@interface NSUrlProtocolTests : XCTestCase

@end

@implementation NSUrlProtocolTests

- (void)setUp
{
    [super setUp];
    [NSURLProtocol registerClass:[NSURLProtocolImpl class]];
}

- (void)tearDown
{
    // Put teardown code here. This method is called after the invocation of each test method in the class.
    [super tearDown];
}

- (void)testExample
{

    TestNeedsToWaitForBlock();

    NSURL *url = [NSURL URLWithString:@"http://www.example.com"];

    NSURLSession *urlsession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
    NSURLSessionDataTask *datatask = [urlsession dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if(error) {
            NSLog(@"error: %@", error);
        }
        else {
            NSLog(@"Success");
        }
        BlockFinished();
    }];
    [datatask resume];

    WaitForBlock();
}

@end

This was tested with XCode 5.0 (5A1412) and iOS 7 simulator.

from nocilla.

gdunkle avatar gdunkle commented on June 19, 2024

I believe the reason this is not working out of the box is because you have to register the LSHTTPStubURLProtocol via the NSUSerSessionConfiguration's protocolClasses array instead of using the registerClass method (see https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSURLSessionConfiguration_class/Reference/Reference.html#//apple_ref/doc/uid/TP40013440-CH1-SW24).

So something like this in your test setup

...
NSURLSessionConfiguration    configuration = [NSURLSessionConfiguration defaultSessionConfiguration];

configuration.protocolClasses=@[[LSHTTPStubURLProtocol class]];
...

from nocilla.

michaelmcguire avatar michaelmcguire commented on June 19, 2024

@gdunkle is correct that the LSHTTPStubURLProtocol is not properly getting registered when using NSURLSession. However, there does not appear to be a way to globally register it for all instances of NSURLSession. This is one of the advantages of NSURLSession is that each instance can have its own set of configuration settings, cookies stores, etc.

This means getting this to work in testing is more difficult. You cannot modify the defaults in the way above because defaultsessionConfiguration returns a copy so modifying the copy does not change the configuration for all users of the class. Instead, you will need to allow the configuration to be set or injected into the class you are testing.

Unfortunately, if you are using Nocilla for automated UI testing, you'll need to figure out how you want to do that for only when running tests (I'm using a preprocessing statements, which I'm not happy with).

from nocilla.

gdunkle avatar gdunkle commented on June 19, 2024

Yeah it's non trivial but definitely doable (at least with AFNetworking). I have it working for my tests by first extending AFHTTPSessionManager

@interface HttpSessionManager : AFHTTPSessionManager
@end

@implementation HttpSessionManager
    -(instancetype)init{
         //this retrieval of base url is specific to my implementation the main thing is you call the super constructor that you're wrapping in the category below
         NSString *baseUrl = [[Properties instance]config][@"baseUrl"];
         self = [super initWithBaseURL:[NSURL URLWithString:baseUrl]];
         if(!self){
            return nil;
         }
         return self;
    }
@end

Then in my test I use a category to add a wrapper around the init method of HttpSessionManager and swizzle the initWithBaseURL and wrap_initWithBaseURL

@interface  HttpSessionManager (AddTestProtocols)
@end

@implementation  HttpSessionManager (AddTestProtocols)
- (instancetype)wrap_initWithBaseURL:(NSURL *)url  sessionConfiguration:(NSURLSessionConfiguration *)configuration{
    if (!configuration) {
        configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    }
    //set the protocol class here!
    configuration.protocolClasses=@[[LSHTTPStubURLProtocol class]];

    return [self wrap_initWithBaseURL:url sessionConfiguration:configuration];
}

//swizzle the initWithBaseURL and wrap_initWithBaseURL 
+ (void)load
{
    Method original, swizzled;

    SEL originalSelector=@selector(initWithBaseURL:sessionConfiguration:);
    SEL swizzledSelector=@selector(wrap_initWithBaseURL:sessionConfiguration:);
    original = class_getInstanceMethod(self, originalSelector);
    swizzled = class_getInstanceMethod(self, swizzledSelector);
    method_exchangeImplementations(original, swizzled);
}
@end

Of course my example is using AFNetworking's extension of the NSUrlSession stuff but I imagine the same principals could be applied.

This method has been working quite well for me.

from nocilla.

timshadel avatar timshadel commented on June 19, 2024

My solution ended up being pretty simple. I already have a class which manages my AFHTTPSessionManager, so I simply added a method to hijack it.

Network.m

+ (AFHTTPSessionManager *)api
{
    return [Network sharedInstance].apiSession;
}

// Used with testing
- (void)hijackSessionWithProtocolClasses:(NSArray *)protocolClasses
{
    NSURLSessionConfiguration *hijackedConfig = self.apiSession.session.configuration;
    hijackedConfig.protocolClasses = protocolClasses;
    [self.apiSession invalidateSessionCancelingTasks:YES];
    self.apiSession = [[AFHTTPSessionManager alloc] initWithBaseURL:self.apiURL sessionConfiguration:hijackedConfig];
}

Then in my tests I just make sure to hijack it:

MyAPIControllerTests.m

- (void)setUp
{
    [[Network sharedInstance] hijackSessionWithProtocolClasses:@[[LSHTTPStubURLProtocol class]]];
}

And then everything worked just fine. I had some trouble referencing LSHTTPStubURLProtocol, since the podspec didn't expose it. But once I changed the spec locally everything was great. I've submitted #54 to update the podspec to let others do something similar.

from nocilla.

jdxcode avatar jdxcode commented on June 19, 2024

@timshadel's fix worked for me! Would be awesome to get this included so we could use AFHTTPSessionManager out of the box!

from nocilla.

kreeger avatar kreeger commented on June 19, 2024

@timshadel looks like your fix was turned down in order to have issue #60 implemented. I can't seem to figure out how to get the solution in #60 to catch my NSURLSession-driven requests. Shouldn't Nocilla raise an NocillaUnexpectedRequest exception if any request calls out that's not stubbed? Because it doesn't seem to be working for me.

MyClass below is a subclass of AFNetworking's AFHTTPSessionManager and it makes a POST call inside the implementation. No big surprises there.

describe(@"MyClass", ^{
    beforeAll(^{
        [[LSNocilla sharedInstance] start];
    });
    afterEach(^{
        [[LSNocilla sharedInstance] clearStubs];
    });
    afterAll(^{
        [[LSNocilla sharedInstance] stop];
    });
    it(@"does the thing", ^AsyncBlock{
        [MyClass makeThatAPICallWithCompletion:^(NSURLSessionDataTask *task, id data, NSError *error) {
            NSInteger statusCode = [(NSHTTPURLResponse *)task.response statusCode];
            expect(data).willNot.beNil();
            expect(statusCode).will.equal(200);
            done();
        }];
    });
});

It lets the request go through just fine; no exception is raised. Do I need to register LSNSURLSessionHook as a hook with Nocilla, similar to how LSASIHTTPRequestHook is registered in the readme?

[[LSNocilla sharedInstance] registerHook:[[LSASIHTTPRequestHook alloc] init]];

LSNSURLSessionHook.h file isn't even exposed through the pod, it seems.

Edit: stranger still, when Travis CI Pro checks out and builds my project, it raises the exception… but not on my local machine, not when running the exact same command that Travis runs (which is an xctool command).

from nocilla.

luisobo avatar luisobo commented on June 19, 2024

@kreeger can you open a new issue with a sample project, please?

I'm closing this issue since it should work as of #60, release in 0.8.0. Please open new issues with any questions or well... issues.

Thanks.

from nocilla.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.