Skip to content

Commit 8d399c7

Browse files
ryanwilsonpaulb777
authored andcommitted
Better mocking for Core Unit Tests (#1467)
* Better mocking for Core Unit Tests This includes NSNotificationCenter and NSUserDefaults to prevent flaky tests and unit tests interfering with each other. * Share variable for intervals being set.
1 parent 5c4b7b5 commit 8d399c7

File tree

4 files changed

+170
-78
lines changed

4 files changed

+170
-78
lines changed

Example/Core/Tests/FIRAnalyticsConfigurationTest.m

Lines changed: 70 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,25 @@
2020
#import <FirebaseCore/FIRAnalyticsConfiguration.h>
2121

2222
@interface FIRAnalyticsConfigurationTest : FIRTestCase
23-
/// A mock for [NSNotificationCenter defaultCenter].
24-
@property(nonatomic, strong) id notificationCenterMock;
23+
/// An observer for NSNotificationCenter.
24+
@property(nonatomic, strong) id observerMock;
25+
26+
@property(nonatomic, strong) NSNotificationCenter *notificationCenter;
2527
@end
2628

2729
@implementation FIRAnalyticsConfigurationTest
2830

2931
- (void)setUp {
3032
[super setUp];
31-
_notificationCenterMock = OCMPartialMock([NSNotificationCenter defaultCenter]);
33+
34+
_observerMock = OCMObserverMock();
35+
_notificationCenter = [NSNotificationCenter defaultCenter];
3236
}
3337

3438
- (void)tearDown {
35-
[_notificationCenterMock stopMocking];
39+
_observerMock = nil;
40+
_notificationCenter = nil;
41+
3642
[super tearDown];
3743
}
3844

@@ -44,49 +50,73 @@ - (void)testSharedInstance {
4450

4551
/// Test that setting the minimum session interval on the singleton fires a notification.
4652
- (void)testMinimumSessionIntervalNotification {
53+
// Pick a value to set as the session interval and verify it's in the userInfo dictionary of the
54+
// posted notification.
55+
NSNumber *sessionInterval = @2601;
56+
57+
// Set up the expectation for the notification.
4758
FIRAnalyticsConfiguration *config = [FIRAnalyticsConfiguration sharedInstance];
48-
[config setMinimumSessionInterval:2601];
4959
NSString *notificationName = kFIRAnalyticsConfigurationSetMinimumSessionIntervalNotification;
50-
OCMVerify([self.notificationCenterMock postNotificationName:notificationName
51-
object:config
52-
userInfo:@{
53-
notificationName : @2601
54-
}]);
60+
[self expectNotificationForObserver:self.observerMock
61+
notificationName:notificationName
62+
object:config
63+
userInfo:@{notificationName : sessionInterval}];
64+
65+
// Trigger the notification.
66+
[config setMinimumSessionInterval:[sessionInterval integerValue]];
67+
68+
// Verify the observer mock.
69+
OCMVerifyAll(self.observerMock);
5570
}
5671

5772
/// Test that setting the minimum session timeout interval on the singleton fires a notification.
5873
- (void)testSessionTimeoutIntervalNotification {
74+
// Pick a value to set as the timeout interval and verify it's in the userInfo dictionary of the
75+
// posted notification.
76+
NSNumber *timeoutInterval = @1000;
77+
78+
// Set up the expectation for the notification.
5979
FIRAnalyticsConfiguration *config = [FIRAnalyticsConfiguration sharedInstance];
60-
[config setSessionTimeoutInterval:1000];
6180
NSString *notificationName = kFIRAnalyticsConfigurationSetSessionTimeoutIntervalNotification;
62-
OCMVerify([self.notificationCenterMock postNotificationName:notificationName
63-
object:config
64-
userInfo:@{
65-
notificationName : @1000
66-
}]);
81+
[self expectNotificationForObserver:self.observerMock
82+
notificationName:notificationName
83+
object:config
84+
userInfo:@{notificationName : timeoutInterval}];
85+
86+
// Trigger the notification.
87+
[config setSessionTimeoutInterval:[timeoutInterval integerValue]];
88+
89+
/// Verify the observer mock.
90+
OCMVerifyAll(self.observerMock);
6791
}
6892

6993
- (void)testSettingAnalyticsCollectionEnabled {
70-
// The ordering matters for these notifications.
71-
[self.notificationCenterMock setExpectationOrderMatters:YES];
72-
73-
// Test setting to enabled.
94+
// Test setting to enabled. The ordering matters for these notifications.
7495
FIRAnalyticsConfiguration *config = [FIRAnalyticsConfiguration sharedInstance];
7596
NSString *notificationName = kFIRAnalyticsConfigurationSetEnabledNotification;
97+
[self.notificationCenter addMockObserver:self.observerMock name:notificationName object:config];
98+
99+
[self.observerMock setExpectationOrderMatters:YES];
100+
[[self.observerMock expect] notificationWithName:notificationName
101+
object:config
102+
userInfo:@{
103+
notificationName : @YES
104+
}];
105+
106+
// Test setting to enabled.
76107
[config setAnalyticsCollectionEnabled:YES];
77-
OCMVerify([self.notificationCenterMock postNotificationName:notificationName
78-
object:config
79-
userInfo:@{
80-
notificationName : @YES
81-
}]);
108+
109+
// Expect the second notification.
110+
[[self.observerMock expect] notificationWithName:notificationName
111+
object:config
112+
userInfo:@{
113+
notificationName : @NO
114+
}];
82115

83116
// Test setting to disabled.
84117
[config setAnalyticsCollectionEnabled:NO];
85-
OCMVerify([self.notificationCenterMock postNotificationName:notificationName
86-
object:config
87-
userInfo:@{
88-
notificationName : @NO
89-
}]);
118+
119+
OCMVerifyAll(self.observerMock);
90120
}
91121

92122
- (void)testSettingAnalyticsCollectionPersistence {
@@ -114,4 +144,14 @@ - (void)testSettingAnalyticsCollectionPersistence {
114144
[userDefaultsMock stopMocking];
115145
}
116146

147+
#pragma mark - Private Test Helpers
148+
149+
- (void)expectNotificationForObserver:(id)observer
150+
notificationName:(NSNotificationName)name
151+
object:(nullable id)object
152+
userInfo:(nullable NSDictionary *)userInfo {
153+
[self.notificationCenter addMockObserver:self.observerMock name:name object:object];
154+
[[observer expect] notificationWithName:name object:object userInfo:userInfo];
155+
}
156+
117157
@end

Example/Core/Tests/FIRAppTest.m

Lines changed: 72 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,9 @@ @interface FIRAppTest : FIRTestCase
5252

5353
@property(nonatomic) id appClassMock;
5454
@property(nonatomic) id optionsInstanceMock;
55-
@property(nonatomic) id notificationCenterMock;
55+
@property(nonatomic) id observerMock;
5656
@property(nonatomic) FIRApp *app;
57+
@property(nonatomic) NSNotificationCenter *notificationCenter;
5758

5859
@end
5960

@@ -65,25 +66,32 @@ - (void)setUp {
6566
[FIRApp resetApps];
6667
_appClassMock = OCMClassMock([FIRApp class]);
6768
_optionsInstanceMock = OCMPartialMock([FIROptions defaultOptions]);
68-
_notificationCenterMock = OCMPartialMock([NSNotificationCenter defaultCenter]);
69+
_observerMock = OCMObserverMock();
70+
71+
// TODO: Remove all usages of defaultCenter in Core, then we can instantiate an instance here to
72+
// inject instead of using defaultCenter.
73+
_notificationCenter = [NSNotificationCenter defaultCenter];
6974
}
7075

7176
- (void)tearDown {
7277
[_appClassMock stopMocking];
7378
[_optionsInstanceMock stopMocking];
74-
[_notificationCenterMock stopMocking];
79+
[_notificationCenter removeObserver:_observerMock];
80+
_observerMock = nil;
81+
_notificationCenter = nil;
7582

7683
[super tearDown];
7784
}
7885

7986
- (void)testConfigure {
8087
NSDictionary *expectedUserInfo =
8188
[self expectedUserInfoWithAppName:kFIRDefaultAppName isDefaultApp:YES];
82-
OCMExpect([self.notificationCenterMock postNotificationName:kFIRAppReadyToConfigureSDKNotification
83-
object:[FIRApp class]
84-
userInfo:expectedUserInfo]);
89+
[self expectNotificationForObserver:self.observerMock
90+
notificationName:kFIRAppReadyToConfigureSDKNotification
91+
object:[FIRApp class]
92+
userInfo:expectedUserInfo];
8593
XCTAssertNoThrow([FIRApp configure]);
86-
OCMVerifyAll(self.notificationCenterMock);
94+
OCMVerifyAll(self.observerMock);
8795

8896
self.app = [FIRApp defaultApp];
8997
XCTAssertNotNil(self.app);
@@ -108,12 +116,13 @@ - (void)testConfigureWithOptions {
108116

109117
NSDictionary *expectedUserInfo =
110118
[self expectedUserInfoWithAppName:kFIRDefaultAppName isDefaultApp:YES];
111-
OCMExpect([self.notificationCenterMock postNotificationName:kFIRAppReadyToConfigureSDKNotification
112-
object:[FIRApp class]
113-
userInfo:expectedUserInfo]);
119+
[self expectNotificationForObserver:self.observerMock
120+
notificationName:kFIRAppReadyToConfigureSDKNotification
121+
object:[FIRApp class]
122+
userInfo:expectedUserInfo];
114123
// default options
115124
XCTAssertNoThrow([FIRApp configureWithOptions:[FIROptions defaultOptions]]);
116-
OCMVerifyAll(self.notificationCenterMock);
125+
OCMVerifyAll(self.observerMock);
117126

118127
self.app = [FIRApp defaultApp];
119128
XCTAssertNotNil(self.app);
@@ -130,12 +139,13 @@ - (void)testConfigureWithCustomizedOptions {
130139
options.APIKey = kCustomizedAPIKey;
131140
NSDictionary *expectedUserInfo =
132141
[self expectedUserInfoWithAppName:kFIRDefaultAppName isDefaultApp:YES];
133-
OCMExpect([self.notificationCenterMock postNotificationName:kFIRAppReadyToConfigureSDKNotification
134-
object:[FIRApp class]
135-
userInfo:expectedUserInfo]);
142+
[self expectNotificationForObserver:self.observerMock
143+
notificationName:kFIRAppReadyToConfigureSDKNotification
144+
object:[FIRApp class]
145+
userInfo:expectedUserInfo];
136146

137147
XCTAssertNoThrow([FIRApp configureWithOptions:options]);
138-
OCMVerifyAll(self.notificationCenterMock);
148+
OCMVerifyAll(self.observerMock);
139149

140150
self.app = [FIRApp defaultApp];
141151
XCTAssertNotNil(self.app);
@@ -158,11 +168,12 @@ - (void)testConfigureWithNameAndOptions {
158168

159169
NSDictionary *expectedUserInfo =
160170
[self expectedUserInfoWithAppName:kFIRTestAppName1 isDefaultApp:NO];
161-
OCMExpect([self.notificationCenterMock postNotificationName:kFIRAppReadyToConfigureSDKNotification
162-
object:[FIRApp class]
163-
userInfo:expectedUserInfo]);
171+
[self expectNotificationForObserver:self.observerMock
172+
notificationName:kFIRAppReadyToConfigureSDKNotification
173+
object:[FIRApp class]
174+
userInfo:expectedUserInfo];
164175
XCTAssertNoThrow([FIRApp configureWithName:kFIRTestAppName1 options:[FIROptions defaultOptions]]);
165-
OCMVerifyAll(self.notificationCenterMock);
176+
OCMVerifyAll(self.observerMock);
166177

167178
XCTAssertTrue([FIRApp allApps].count == 1);
168179
self.app = [FIRApp appNamed:kFIRTestAppName1];
@@ -179,11 +190,16 @@ - (void)testConfigureWithNameAndCustomizedOptions {
179190
FIROptions *newOptions = [options copy];
180191
newOptions.deepLinkURLScheme = kDeepLinkURLScheme;
181192

193+
// Set up notification center observer for verifying notifications.
194+
[self.notificationCenter addMockObserver:self.observerMock
195+
name:kFIRAppReadyToConfigureSDKNotification
196+
object:[FIRApp class]];
197+
182198
NSDictionary *expectedUserInfo1 =
183199
[self expectedUserInfoWithAppName:kFIRTestAppName1 isDefaultApp:NO];
184-
OCMExpect([self.notificationCenterMock postNotificationName:kFIRAppReadyToConfigureSDKNotification
185-
object:[FIRApp class]
186-
userInfo:expectedUserInfo1]);
200+
[[self.observerMock expect] notificationWithName:kFIRAppReadyToConfigureSDKNotification
201+
object:[FIRApp class]
202+
userInfo:expectedUserInfo1];
187203
XCTAssertNoThrow([FIRApp configureWithName:kFIRTestAppName1 options:newOptions]);
188204
XCTAssertTrue([FIRApp allApps].count == 1);
189205
self.app = [FIRApp appNamed:kFIRTestAppName1];
@@ -196,11 +212,13 @@ - (void)testConfigureWithNameAndCustomizedOptions {
196212

197213
NSDictionary *expectedUserInfo2 =
198214
[self expectedUserInfoWithAppName:kFIRTestAppName2 isDefaultApp:NO];
199-
OCMExpect([self.notificationCenterMock postNotificationName:kFIRAppReadyToConfigureSDKNotification
200-
object:[FIRApp class]
201-
userInfo:expectedUserInfo2]);
215+
[[self.observerMock expect] notificationWithName:kFIRAppReadyToConfigureSDKNotification
216+
object:[FIRApp class]
217+
userInfo:expectedUserInfo2];
218+
219+
[self.observerMock setExpectationOrderMatters:YES];
202220
XCTAssertNoThrow([FIRApp configureWithName:kFIRTestAppName2 options:customizedOptions]);
203-
OCMVerifyAll(self.notificationCenterMock);
221+
OCMVerifyAll(self.observerMock);
204222

205223
XCTAssertTrue([FIRApp allApps].count == 2);
206224
self.app = [FIRApp appNamed:kFIRTestAppName2];
@@ -241,12 +259,15 @@ - (void)testDeleteApp {
241259
[FIRApp configure];
242260
self.app = [FIRApp defaultApp];
243261
XCTAssertTrue([FIRApp allApps].count == 1);
262+
[self expectNotificationForObserver:self.observerMock
263+
notificationName:kFIRAppDeleteNotification
264+
object:[FIRApp class]
265+
userInfo:[OCMArg any]];
244266
[self.app deleteApp:^(BOOL success) {
245267
XCTAssertTrue(success);
246268
}];
247-
OCMVerify([self.notificationCenterMock postNotificationName:kFIRAppDeleteNotification
248-
object:[FIRApp class]
249-
userInfo:[OCMArg any]]);
269+
270+
OCMVerifyAll(self.observerMock);
250271
XCTAssertTrue(self.app.alreadySentDeleteNotification);
251272
XCTAssertTrue([FIRApp allApps].count == 0);
252273
}
@@ -671,16 +692,27 @@ - (void)testGlobalDataCollectionClearedAfterDelete {
671692
- (void)testGlobalDataCollectionNoDiagnosticsSent {
672693
[FIRApp configure];
673694

695+
// Add an observer for the diagnostics notification - both with and without an object to ensure it
696+
// catches it either way. Currently no object is sent, but in the future that could change.
697+
[self.notificationCenter addMockObserver:self.observerMock
698+
name:kFIRAppDiagnosticsNotification
699+
object:nil];
700+
[self.notificationCenter addMockObserver:self.observerMock
701+
name:kFIRAppDiagnosticsNotification
702+
object:OCMOCK_ANY];
703+
674704
// Stub out reading from user defaults since stubbing out the BOOL has issues. If the data
675705
// collection switch is disabled, the `sendLogs` call should return immediately and not fire a
676706
// notification.
677707
OCMStub([self.appClassMock readDataCollectionSwitchFromUserDefaultsForApp:OCMOCK_ANY])
678708
.andReturn(@NO);
679-
OCMReject([self.notificationCenterMock postNotificationName:kFIRAppDiagnosticsNotification
680-
object:OCMOCK_ANY
681-
userInfo:OCMOCK_ANY]);
709+
682710
NSError *error = [NSError errorWithDomain:@"com.firebase" code:42 userInfo:nil];
683711
[[FIRApp defaultApp] sendLogsWithServiceName:@"Service" version:@"Version" error:error];
712+
713+
// The observer mock is strict and will raise an exception when an unexpected notification is
714+
// received.
715+
OCMVerifyAll(self.observerMock);
684716
}
685717

686718
#pragma mark - Analytics Flag Tests
@@ -767,6 +799,14 @@ - (void)testMultipleLibraries {
767799

768800
#pragma mark - private
769801

802+
- (void)expectNotificationForObserver:(id)observer
803+
notificationName:(NSNotificationName)name
804+
object:(nullable id)object
805+
userInfo:(nullable NSDictionary *)userInfo {
806+
[self.notificationCenter addMockObserver:observer name:name object:object];
807+
[[observer expect] notificationWithName:name object:object userInfo:userInfo];
808+
}
809+
770810
- (NSDictionary<NSString *, NSObject *> *)expectedUserInfoWithAppName:(NSString *)name
771811
isDefaultApp:(BOOL)isDefaultApp {
772812
return @{

0 commit comments

Comments
 (0)