// Copyright 2018 Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #import "FirebaseCore/Tests/Unit/FIRTestCase.h" #import "FirebaseCore/Sources/FIRComponentContainerInternal.h" #import "FirebaseCore/Sources/Private/FIRComponentType.h" #import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h" #import "FirebaseCore/Tests/Unit/FIRTestComponents.h" #import "SharedTestUtilities/FIROptionsMock.h" /// Internally exposed methods and properties for testing. @interface FIRComponentContainer (TestInternal) @property(nonatomic, strong) NSMutableDictionary *components; @property(nonatomic, strong) NSMutableDictionary *cachedInstances; + (void)registerAsComponentRegistrant:(Class)klass inSet:(NSMutableSet *)allRegistrants; - (instancetype)initWithApp:(FIRApp *)app registrants:(NSMutableSet *)allRegistrants; @end @interface FIRComponentContainer (TestInternalImplementations) - (instancetype)initWithApp:(FIRApp *)app components:(NSDictionary *)components; @end @implementation FIRComponentContainer (TestInternalImplementations) - (instancetype)initWithApp:(FIRApp *)app components:(NSDictionary *)components { self = [self initWithApp:app registrants:[[NSMutableSet alloc] init]]; if (self) { self.components = [components mutableCopy]; } return self; } @end @interface FIRComponentContainerTest : FIRTestCase { FIRApp *_hostApp; } @end @implementation FIRComponentContainerTest - (void)tearDown { _hostApp = nil; [super tearDown]; } #pragma mark - Registration Tests - (void)testRegisteringConformingClass { NSMutableSet *allRegistrants = [NSMutableSet set]; Class testClass = [FIRTestClass class]; [FIRComponentContainer registerAsComponentRegistrant:testClass inSet:allRegistrants]; XCTAssertTrue([allRegistrants containsObject:testClass]); } - (void)testComponentsPopulatedOnInit { FIRComponentContainer *container = [self containerWithRegistrants:@[ [FIRTestClass class] ]]; // Verify that the block is stored. NSString *protocolName = NSStringFromProtocol(@protocol(FIRTestProtocol)); FIRComponentCreationBlock creationBlock = container.components[protocolName]; XCTAssertNotNil(creationBlock); } #pragma mark - Caching Tests - (void)testInstanceCached { FIRComponentContainer *container = [self containerWithRegistrants:@[ [FIRTestClassCached class] ]]; // Fetch an instance for `FIRTestProtocolCached`, then fetch it again to assert it's cached. id instance1 = FIR_COMPONENT(FIRTestProtocolCached, container); XCTAssertNotNil(instance1); id instance2 = FIR_COMPONENT(FIRTestProtocolCached, container); XCTAssertNotNil(instance2); XCTAssertEqual(instance1, instance2); } - (void)testInstanceNotCached { FIRComponentContainer *container = [self containerWithRegistrants:@[ [FIRTestClass class] ]]; // Retrieve an instance from the container, then fetch it again and ensure it's not the same // instance. id instance1 = FIR_COMPONENT(FIRTestProtocol, container); XCTAssertNotNil(instance1); id instance2 = FIR_COMPONENT(FIRTestProtocol, container); XCTAssertNotNil(instance2); XCTAssertNotEqual(instance1, instance2); } - (void)testRemoveAllCachedInstances { FIRComponentContainer *container = [self containerWithRegistrants:@[ [FIRTestClass class], [FIRTestClassCached class], [FIRTestClassEagerCached class], [FIRTestClassCachedWithDep class] ]]; // Retrieve an instance of FIRTestClassCached to ensure it's cached. id cachedInstance1 = FIR_COMPONENT(FIRTestProtocolCached, container); id eagerInstance1 = FIR_COMPONENT(FIRTestProtocolEagerCached, container); // FIRTestClassEagerCached and FIRTestClassCached instances should be cached at this point. XCTAssertTrue(container.cachedInstances.count == 2); // Remove the instances and verify cachedInstances is empty, and that new instances returned from // the container don't match the old ones. [container removeAllCachedInstances]; XCTAssertTrue(container.cachedInstances.count == 0); id cachedInstance2 = FIR_COMPONENT(FIRTestProtocolCached, container); XCTAssertNotEqual(cachedInstance1, cachedInstance2); id eagerInstance2 = FIR_COMPONENT(FIRTestProtocolEagerCached, container); XCTAssertNotEqual(eagerInstance1, eagerInstance2); } - (void)testRemoveAllComponents { FIRComponentContainer *container = [self containerWithRegistrants:@[ [FIRTestClass class], [FIRTestClassCached class], [FIRTestClassEagerCached class], [FIRTestClassCachedWithDep class] ]]; // Retrieve an instance of FIRTestClassCached to ensure it's cached. id cachedInstance1 = FIR_COMPONENT(FIRTestProtocolCached, container); XCTAssertNotNil(cachedInstance1); id eagerInstance1 = FIR_COMPONENT(FIRTestProtocolEagerCached, container); XCTAssertNotNil(eagerInstance1); // FIRTestClassEagerCached and FIRTestClassCached instances should be cached at this point. XCTAssertTrue(container.cachedInstances.count == 2); // Remove all components. [container removeAllComponents]; // Remove the instances. [container removeAllCachedInstances]; // Verify that no new instances are created. id cachedInstance2 = FIR_COMPONENT(FIRTestProtocolCached, container); XCTAssertNil(cachedInstance2); id eagerInstance2 = FIR_COMPONENT(FIRTestProtocolEagerCached, container); XCTAssertNil(eagerInstance2); } #pragma mark - Instantiation Tests - (void)testEagerInstantiation { // Create a container with `FIRTestClassEagerCached` as a registrant, which provides the // implementation for `FIRTestProtocolEagerCached` and requires eager instantiation as well as // caching so the test can verify it was eagerly instantiated. FIRComponentContainer *container = [self containerWithRegistrants:@[ [FIRTestClassEagerCached class] ]]; NSString *protocolName = NSStringFromProtocol(@protocol(FIRTestProtocolEagerCached)); XCTAssertNotNil(container.cachedInstances[protocolName]); } #pragma mark - Input Validation Tests - (void)testProtocolAlreadyRegistered { // Register two classes that provide the same protocol. Only one should be stored, and there // should be a log stating that the protocol has already been registered. Right now there's no // guarantee which one will be registered first since it's an NSSet under the hood, but that could // change in the future. // TODO(wilsonryan): Assert that the log gets called warning that it's already been registered. FIRComponentContainer *container = [self containerWithRegistrants:@[ [FIRTestClass class], [FIRTestClassDuplicate class] ]]; XCTAssert(container.components.count == 1); } #pragma mark - Dependency Tests - (void)testDependencyDoesntBlock { /// Test a class that has a dependency, and fetching doesn't block the internal queue. FIRComponentContainer *container = [self containerWithRegistrants:@[ [FIRTestClassCached class], [FIRTestClassCachedWithDep class] ]]; XCTAssert(container.components.count == 2); id instanceWithDep = FIR_COMPONENT(FIRTestProtocolCachedWithDep, container); XCTAssertNotNil(instanceWithDep); } - (void)testDependencyRemoveAllCachedInstancesDoesntBlock { /// Test a class that has a dependency, and fetching doesn't block the internal queue. FIRComponentContainer *container = [self containerWithRegistrants:@[ [FIRTestClassCached class], [FIRTestClassCachedWithDep class] ]]; XCTAssert(container.components.count == 2); id instanceWithDep = FIR_COMPONENT(FIRTestProtocolCachedWithDep, container); XCTAssertNotNil(instanceWithDep); XCTAssertNotNil(instanceWithDep.testProperty); // Both `instanceWithDep` and `testProperty` should be cached now. XCTAssertTrue(container.cachedInstances.count == 2); // Remove the instances and verify cachedInstances is empty, and doesn't block the queue. [container removeAllCachedInstances]; XCTAssertTrue(container.cachedInstances.count == 0); } #pragma mark - Convenience Methods /// Create a container that has registered the test class. - (FIRComponentContainer *)containerWithRegistrants:(NSArray *)registrants { FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:kGoogleAppID GCMSenderID:kGCMSenderID]; _hostApp = [[FIRApp alloc] initInstanceWithName:@"fake_app" options:options]; NSMutableSet *allRegistrants = [NSMutableSet set]; // Initialize the container with the test classes. for (Class c in registrants) { [FIRComponentContainer registerAsComponentRegistrant:c inSet:allRegistrants]; } // Override the app's container with the newly instantiated container. FIRComponentContainer *container = [[FIRComponentContainer alloc] initWithApp:_hostApp registrants:allRegistrants]; _hostApp.container = container; // Instantiate all the components that were eagerly registered now that all other properties are // configured. [container instantiateEagerComponents]; return container; } @end