Skip to content

Commit dbafa4b

Browse files
committed
Add 2 mocking frameworks and 2 testing bundles
* Add mocking framework called `OneSignalCoreMocks` to simplify working with `OneSignalCore` in unit tests - Add helper method `clearUserDefaults()` - Create `MockOneSignalClient` class to replace `OneSignalClient` in unit tests * Add mocking framework called `OneSignalUserMocks` to simplify working with `OneSignalUser` in unit tests - Add some helper methods to reset state between tests, WIP * Add unit testing bundle `OneSignalCoreTests`, meant to test `OneSignalCore` - Add one test `testNotificationJson`, to get started * Add unit testing bundle `OneSignalUserTests`, meant to test `OneSignalUser` - Add one test `testLoginSetsExternalId`, to get started
1 parent 63c4976 commit dbafa4b

File tree

10 files changed

+1822
-55
lines changed

10 files changed

+1822
-55
lines changed

iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj

Lines changed: 1250 additions & 55 deletions
Large diffs are not rendered by default.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "1520"
4+
version = "1.7">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES">
8+
</BuildAction>
9+
<TestAction
10+
buildConfiguration = "Debug"
11+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
12+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
13+
shouldUseLaunchSchemeArgsEnv = "YES"
14+
shouldAutocreateTestPlan = "YES">
15+
<Testables>
16+
<TestableReference
17+
skipped = "NO"
18+
parallelizable = "YES">
19+
<BuildableReference
20+
BuildableIdentifier = "primary"
21+
BlueprintIdentifier = "3CC063A02B6D7A8D002BB07F"
22+
BuildableName = "OneSignalCoreTests.xctest"
23+
BlueprintName = "OneSignalCoreTests"
24+
ReferencedContainer = "container:OneSignal.xcodeproj">
25+
</BuildableReference>
26+
</TestableReference>
27+
</Testables>
28+
</TestAction>
29+
<LaunchAction
30+
buildConfiguration = "Debug"
31+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
32+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
33+
launchStyle = "0"
34+
useCustomWorkingDirectory = "NO"
35+
ignoresPersistentStateOnLaunch = "NO"
36+
debugDocumentVersioning = "YES"
37+
debugServiceExtension = "internal"
38+
allowLocationSimulation = "YES">
39+
</LaunchAction>
40+
<ProfileAction
41+
buildConfiguration = "Release"
42+
shouldUseLaunchSchemeArgsEnv = "YES"
43+
savedToolIdentifier = ""
44+
useCustomWorkingDirectory = "NO"
45+
debugDocumentVersioning = "YES">
46+
</ProfileAction>
47+
<AnalyzeAction
48+
buildConfiguration = "Debug">
49+
</AnalyzeAction>
50+
<ArchiveAction
51+
buildConfiguration = "Release"
52+
revealArchiveInOrganizer = "YES">
53+
</ArchiveAction>
54+
</Scheme>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "1520"
4+
version = "1.7">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES">
8+
</BuildAction>
9+
<TestAction
10+
buildConfiguration = "Debug"
11+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
12+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
13+
shouldUseLaunchSchemeArgsEnv = "YES"
14+
shouldAutocreateTestPlan = "YES">
15+
<Testables>
16+
<TestableReference
17+
skipped = "NO"
18+
parallelizable = "YES">
19+
<BuildableReference
20+
BuildableIdentifier = "primary"
21+
BlueprintIdentifier = "3CC063EA2B6D7FE8002BB07F"
22+
BuildableName = "OneSignalUserTests.xctest"
23+
BlueprintName = "OneSignalUserTests"
24+
ReferencedContainer = "container:OneSignal.xcodeproj">
25+
</BuildableReference>
26+
</TestableReference>
27+
</Testables>
28+
</TestAction>
29+
<LaunchAction
30+
buildConfiguration = "Debug"
31+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
32+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
33+
launchStyle = "0"
34+
useCustomWorkingDirectory = "NO"
35+
ignoresPersistentStateOnLaunch = "NO"
36+
debugDocumentVersioning = "YES"
37+
debugServiceExtension = "internal"
38+
allowLocationSimulation = "YES">
39+
</LaunchAction>
40+
<ProfileAction
41+
buildConfiguration = "Release"
42+
shouldUseLaunchSchemeArgsEnv = "YES"
43+
savedToolIdentifier = ""
44+
useCustomWorkingDirectory = "NO"
45+
debugDocumentVersioning = "YES">
46+
</ProfileAction>
47+
<AnalyzeAction
48+
buildConfiguration = "Debug">
49+
</AnalyzeAction>
50+
<ArchiveAction
51+
buildConfiguration = "Release"
52+
revealArchiveInOrganizer = "YES">
53+
</ArchiveAction>
54+
</Scheme>
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
Modified MIT License
3+
Copyright 2024 OneSignal
4+
Permission is hereby granted, free of charge, to any person obtaining a copy
5+
of this software and associated documentation files (the "Software"), to deal
6+
in the Software without restriction, including without limitation the rights
7+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
copies of the Software, and to permit persons to whom the Software is
9+
furnished to do so, subject to the following conditions:
10+
1. The above copyright notice and this permission notice shall be included in
11+
all copies or substantial portions of the Software.
12+
2. All copies of substantial portions of the Software may only be used in connection
13+
with services provided by OneSignal.
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
THE SOFTWARE.
21+
*/
22+
23+
import Foundation
24+
import OneSignalCore
25+
26+
/**
27+
This mock client is still adapting, and some logic from the existing OneSignalClientOverrider have been brought to here.
28+
*/
29+
@objc
30+
public class MockOneSignalClient: NSObject, IOneSignalClient {
31+
public let executionQueue: DispatchQueue = DispatchQueue(label: "com.onesignal.execution")
32+
33+
var mockResponses: [String: [String: Any]] = [:]
34+
public var lastHTTPRequest: OneSignalRequest?
35+
public var networkRequestCount = 0
36+
public var executedRequests: [OneSignalRequest] = []
37+
public var executeInstantaneously = true
38+
39+
var remoteParamsResponse: [String: Any]?
40+
var shouldUseProvisionalAuthorization = false // new in iOS 12 (aka Direct to History)
41+
var remoteParamsOutcomes: [String: Any] = [:]
42+
43+
/** May add to or change this default remote params response*/
44+
public func getRemoteParamsResponse() -> [String: Any] {
45+
return remoteParamsResponse ?? [
46+
IOS_FBA: true,
47+
IOS_USES_PROVISIONAL_AUTHORIZATION: shouldUseProvisionalAuthorization,
48+
IOS_RECEIVE_RECEIPTS_ENABLE: true,
49+
"outcomes": remoteParamsOutcomes
50+
]
51+
}
52+
53+
public func enableOutcomes() {
54+
remoteParamsOutcomes = [
55+
"direct": [
56+
"enabled": true
57+
],
58+
"indirect": [
59+
"notification_attribution": [
60+
"minutes_since_displayed": 1440,
61+
"limit": 10
62+
],
63+
"enabled": true
64+
],
65+
"unattributed": [
66+
"enabled": true
67+
]
68+
]
69+
}
70+
71+
// Temp. method to log info while building unit tests
72+
@objc public func logSelfInfo() {
73+
print("🧪 MockOneSignalClient with executionQueue \(executionQueue)")
74+
}
75+
76+
public func reset() {
77+
mockResponses = [:]
78+
lastHTTPRequest = nil
79+
networkRequestCount = 0
80+
executedRequests.removeAll()
81+
executeInstantaneously = true
82+
remoteParamsResponse = nil
83+
shouldUseProvisionalAuthorization = false
84+
remoteParamsOutcomes = [:]
85+
}
86+
87+
public func execute(_ request: OneSignalRequest, onSuccess successBlock: @escaping OSResultSuccessBlock, onFailure failureBlock: @escaping OSFailureBlock) {
88+
print("🧪 MockOneSignalClient execute called")
89+
90+
executedRequests.append(request)
91+
92+
if executeInstantaneously {
93+
finishExecutingRequest(request, onSuccess: successBlock, onFailure: failureBlock)
94+
} else {
95+
executionQueue.async {
96+
self.finishExecutingRequest(request, onSuccess: successBlock, onFailure: failureBlock)
97+
}
98+
}
99+
}
100+
101+
func finishExecutingRequest(_ request: OneSignalRequest, onSuccess successBlock: OSResultSuccessBlock, onFailure failureBlock: OSFailureBlock) {
102+
103+
// TODO: This entire method needs to contained within the equivalent of @synchronized ❗️
104+
print("🧪 completing HTTP request: \(request)")
105+
106+
// TODO: Check for existence of app_id in the request and fail if not.
107+
108+
self.didCompleteRequest(request)
109+
110+
// Switch between types of requests with mock responses
111+
if request.isKind(of: OSRequestGetIosParams.self) {
112+
// send a mock remote params response
113+
successBlock(["mockTodo": "responseTodo"])
114+
}
115+
if (mockResponses[String(describing: request)]) != nil {
116+
successBlock(mockResponses[String(describing: request)])
117+
} else {
118+
print("🧪 cannot find a mock response for request: \(request)")
119+
}
120+
}
121+
122+
func didCompleteRequest(_ request: OneSignalRequest) {
123+
networkRequestCount += 1
124+
125+
print("🧪 didCompleteRequest url(\(networkRequestCount)): \(String(describing: request.urlRequest().url)) params: \(String(describing: request.parameters))")
126+
127+
lastHTTPRequest = request
128+
}
129+
130+
/** This is not currently hooked up to anything to run */
131+
@objc public func runBackgroundThreads() {
132+
// Obj-C implementation: dispatch_sync(executionQueue, ^{})
133+
executionQueue.sync {}
134+
}
135+
136+
public func setMockResponseForRequest(request: String, response: [String: Any]) {
137+
mockResponses[request] = response
138+
}
139+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
Modified MIT License
3+
Copyright 2024 OneSignal
4+
Permission is hereby granted, free of charge, to any person obtaining a copy
5+
of this software and associated documentation files (the "Software"), to deal
6+
in the Software without restriction, including without limitation the rights
7+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
copies of the Software, and to permit persons to whom the Software is
9+
furnished to do so, subject to the following conditions:
10+
1. The above copyright notice and this permission notice shall be included in
11+
all copies or substantial portions of the Software.
12+
2. All copies of substantial portions of the Software may only be used in connection
13+
with services provided by OneSignal.
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
THE SOFTWARE.
21+
*/
22+
#import <Foundation/Foundation.h>
23+
24+
//! Project version number for OneSignalCoreMocks.
25+
FOUNDATION_EXPORT double OneSignalCoreMocksVersionNumber;
26+
27+
//! Project version string for OneSignalCoreMocks.
28+
FOUNDATION_EXPORT const unsigned char OneSignalCoreMocksVersionString[];
29+
30+
// In this header, you should import all the public headers of your framework using statements like #import <OneSignalCoreMocks/PublicHeader.h>
31+
32+
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
Modified MIT License
3+
Copyright 2024 OneSignal
4+
Permission is hereby granted, free of charge, to any person obtaining a copy
5+
of this software and associated documentation files (the "Software"), to deal
6+
in the Software without restriction, including without limitation the rights
7+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
copies of the Software, and to permit persons to whom the Software is
9+
furnished to do so, subject to the following conditions:
10+
1. The above copyright notice and this permission notice shall be included in
11+
all copies or substantial portions of the Software.
12+
2. All copies of substantial portions of the Software may only be used in connection
13+
with services provided by OneSignal.
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
THE SOFTWARE.
21+
*/
22+
23+
import Foundation
24+
import OneSignalCore
25+
26+
@objc
27+
public class OneSignalCoreMocks: NSObject {
28+
29+
public static func clearUserDefaults() {
30+
guard let userDefaults = OneSignalUserDefaults.initStandard().userDefaults else {
31+
return
32+
}
33+
let dictionary = userDefaults.dictionaryRepresentation()
34+
for key in dictionary.keys {
35+
userDefaults.removeObject(forKey: key)
36+
}
37+
38+
guard let sharedUserDefaults = OneSignalUserDefaults.initShared().userDefaults else {
39+
return
40+
}
41+
let sharedDictionary = sharedUserDefaults.dictionaryRepresentation()
42+
for key in sharedDictionary.keys {
43+
sharedUserDefaults.removeObject(forKey: key)
44+
}
45+
}
46+
}

0 commit comments

Comments
 (0)