atomicobject / objection

A lightweight dependency injection framework for Objective-C
http://www.objection-framework.org
MIT License
1.85k stars 223 forks source link

Reseting Objection's global context storage caches #76

Open jonah-williams opened 10 years ago

jonah-williams commented 10 years ago

Hi folks, I ran across a case where Objection doesn't give me the control I need during testing. I'm curious if anyone else has seen the same issue and if I can propose an appropriate solution in line with the design of the rest of the framework.

I'm writing integration/functional tests where I deliberately exercise many classes, several of which have been instantiated via Objection. Some of these classes are registered with Objection as singletons (via objection_register_singleton()) and as such multiple other classes might have been given references to the same instance. These singletons introduce our old enemy global mutable state. I want to make sure that state is reset between test cases so that the order in which tests are run cannot influence their outcome (for example a queue of pending network requests might need to be emptied between tests). As is I might manually reset these states but that requires that my test be aware of, able to access, and coupled to all of these dependencies. I would rather not tightly couple my test of a high level interface to implementation details of classes deep in the object graph the test produces. Alternately I see [JSObjection reset] which clears the global context. Unfortunately this destroys the JSObjectionInjectorEntrys in that context and the scope with which they were registered as well. Since objection_register_singleton implements +initialize I can't easily re-run those registration calls. As a result classes which were once singletons no longer are. I considered creating a new JSObjectionInjector instance without the same global context but allowing an injector's global context to not match JSObjection's gObjectionContext seems like a violation of Objection's expected behavior. In addition there's no public mechanism to obtain a JSObjectionInjector's set of modules so I cannot currently init a new injector with the same module set but an empty context. I can obviously reach into Objection's internals to make this work for now but is there an appropriate public interface we could expose to solve this problem?

What is the intended use of [JSObjection reset]? Is it important that it clear the scope with which classes are registered? Is so does it seem appropriate to introduce a new method which discards the _storageCache from each JSObjectionInjectorEntry in the JSObjection gObjectionContext?

As an example I'd like to be able to write something like:

@interface TestObserver : XCTestObserver
@end

@implementation TestObserver

- (void)testCaseDidStart:(XCTestRun *)testRun {
    [JSObjection resetSingletons];
    [super testCaseDidStart:testRun];
}

@end
jdewind commented 10 years ago

Jonah,

Singletons are only "global" within the context of an injector. Which means that if you were to create a new injector you would get a new set of singletons.

Unless, of course, you are referring to whether a class is defined as a singleton or not. Then yes, that is globally defined.

jonah-williams commented 10 years ago

@dewind you're right, thanks. Working out an implementation I see that I don't need to touch JSObjection's state. It would however be helpful if I could get the set of modules off of the current default injector. That way my test runner does not need to know how to assemble the same set of modules configured in my app and can instead:

- (void)testCaseDidStart:(XCTestRun *)testRun {
    NSArray *modules = [[JSObjection defaultInjector] modules];
    JSObjectionInjector *injector = [JSObjection createInjectorWithModulesArray:modules];
    [JSObjection setDefaultInjector:injector];
    [super testCaseDidStart:testRun];
}