Skip to content

Commit b9baa3a

Browse files
authored
Merge pull request #21 from NordicSemiconductor/develop
Version 0.11.0
2 parents 63cde25 + f218834 commit b9baa3a

File tree

10 files changed

+98
-37
lines changed

10 files changed

+98
-37
lines changed

CoreBluetoothMock.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'CoreBluetoothMock'
3-
s.version = '0.10.0'
3+
s.version = '0.11.0'
44
s.summary = 'Mocking library for CoreBluetooth.'
55

66
s.description = <<-DESC

CoreBluetoothMock/Classes/CBMCentralManagerFactory.swift

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
import CoreBluetooth
3232

33-
/// The factory that instantiates the CBMCentralManager object.
33+
/// The factory that instantiates the `CBMCentralManager` object.
3434
/// The factory may be used to automatically instantiate either a native
3535
/// or mock implementation based on the environment. You may also
3636
/// instantiate the `CBMCentralManagerMock` or `CBMCentralManagerNative` without
@@ -56,12 +56,12 @@ public class CBMCentralManagerFactory {
5656
public static var simulateFeaturesSupport: ((_ features: CBMCentralManager.Feature) -> Bool)?
5757
#endif
5858

59-
/// Returns the implementation of CBCentralManager, depending on the environment.
59+
/// Returns the implementation of `CBCentralManager`, depending on the environment.
6060
/// On a simulator, or when the `forceMock` flag is enabled, the mock
6161
/// implementation is returned, otherwise the native one.
6262
/// - Parameters:
6363
/// - forceMock: A flag to force mocking also on physical device.
64-
/// - Returns: The implementation of CBCentralManager.
64+
/// - Returns: The implementation of `CBCentralManager`.
6565
public static func instance(forceMock: Bool = false) -> CBMCentralManager {
6666
#if targetEnvironment(simulator)
6767
return CBMCentralManagerMock()
@@ -72,15 +72,15 @@ public class CBMCentralManagerFactory {
7272
#endif
7373
}
7474

75-
/// Returns the implementation of CBCentralManager, depending on the environment.
75+
/// Returns the implementation of `CBCentralManager`, depending on the environment.
7676
/// On a simulator, or when the `forceMock` flag is enabled, the mock
7777
/// implementation is returned, otherwise the native one.
7878
/// - Parameters:
7979
/// - delegate: The delegate that will receive central role events.
8080
/// - queue: The dispatch queue on which the events will be dispatched.
8181
/// If <i>nil</i>, the main queue will be used.
8282
/// - forceMock: A flag to force mocking also on a physical device.
83-
/// - Returns: The implementation of CBCentralManager.
83+
/// - Returns: The implementation of `CBCentralManager`.
8484
public static func instance(delegate: CBMCentralManagerDelegate?,
8585
queue: DispatchQueue?,
8686
forceMock: Bool = false) -> CBMCentralManager {
@@ -93,7 +93,7 @@ public class CBMCentralManagerFactory {
9393
#endif
9494
}
9595

96-
/// Returns the implementation of CBCentralManager, depending on the environment.
96+
/// Returns the implementation of `CBCentralManager`, depending on the environment.
9797
/// On a simulator, or when the `forceMock` flag is enabled, the mock
9898
/// implementation is returned, otherwise the native one.
9999
/// - Parameters:
@@ -102,7 +102,7 @@ public class CBMCentralManagerFactory {
102102
/// If <i>nil</i>, the main queue will be used.
103103
/// - options: An optional dictionary specifying options for the manager.
104104
/// - forceMock: A flag to force mocking also on a physical device.
105-
/// - Returns: The implementation of CBCentralManager.
105+
/// - Returns: The implementation of `CBCentralManager`.
106106
public static func instance(delegate: CBMCentralManagerDelegate?,
107107
queue: DispatchQueue?,
108108
options: [String : Any]?,
@@ -115,5 +115,4 @@ public class CBMCentralManagerFactory {
115115
CBMCentralManagerNative(delegate: delegate, queue: queue, options: options)
116116
#endif
117117
}
118-
119118
}

CoreBluetoothMock/Classes/CBMCentralManagerMock.swift

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,13 @@ public class CBMCentralManagerMock: NSObject, CBMCentralManager {
8282
/// A flag set to true few milliseconds after the manager is created.
8383
/// Some features, like the state or retrieving peripherals are not
8484
/// available when manager hasn't been initialized yet.
85-
private var initialized: Bool = false
85+
private var initialized: Bool {
86+
// This method returns true if the manager is added to
87+
// the list of managers.
88+
// Calling tearDownSimulation() will remove all managers
89+
// from that list, making them uninitialized again.
90+
return CBMCentralManagerMock.managers.contains { $0.ref == self }
91+
}
8692

8793
// MARK: - Initializers
8894

@@ -130,24 +136,41 @@ public class CBMCentralManagerMock: NSObject, CBMCentralManager {
130136
}
131137

132138
private func initialize() {
133-
if CBMCentralManagerMock.peripherals.isEmpty {
134-
NSLog("Warning: No simulated peripherals. Call simulatePeripherals(:) before creating central manager")
139+
if CBMCentralManagerMock.managerState == .poweredOn &&
140+
CBMCentralManagerMock.peripherals.isEmpty {
141+
NSLog("Warning: No simulated peripherals. " +
142+
"Call simulatePeripherals(:) before creating central manager")
135143
}
136144
// Let's say initialization takes 10 ms. Less or more.
137145
queue.asyncAfter(deadline: .now() + .milliseconds(10)) { [weak self] in
138146
if let self = self {
139147
CBMCentralManagerMock.managers.append(WeakRef(self))
140-
self.initialized = true
141148
self.delegate?.centralManagerDidUpdateState(self)
142149
}
143150
}
144151
}
145152

153+
/// Removes all active central manager instances and peripherals from the
154+
/// simulation, resetting it to the initial state.
155+
///
156+
/// Use this to tear down your mocks between tests, e.g. in `tearDownWithError()`.
157+
/// All manager delegates will receive a `.unknown` state update.
158+
public static func tearDownSimulation() {
159+
// Set the state of all currently existing cenral manager instances to
160+
// .unknown, which will make them invalid.
161+
managerState = .unknown
162+
// Remove all central manager instances.
163+
managers.removeAll()
164+
// Set the manager state to powered Off.
165+
managerState = .poweredOff
166+
peripherals.removeAll()
167+
}
168+
146169
// MARK: - Central manager simulation methods
147170

148171
/// Sets the initial state of the Bluetooth central manager.
149172
///
150-
/// This method should only be called ones, before any `CBMCentralManagerMock`
173+
/// This method should only be called ones, before any central manager
151174
/// is created. By default, the initial state is `.poweredOff`.
152175
/// - Parameter state: The initial state of the central manager.
153176
public static func simulateInitialState(_ state: CBMManagerState) {
@@ -157,14 +180,18 @@ public class CBMCentralManagerMock: NSObject, CBMCentralManager {
157180
/// This method sets a list of simulated peripherals.
158181
///
159182
/// Peripherals added using this method will be available for scanning
160-
/// and connecting, depending on their proximity. Use
161-
/// peripheral's `simulateProximity(of:didChangeTo:)` to modify proximity.
183+
/// and connecting, depending on their proximity. Use peripheral's
184+
/// `simulateProximity(of:didChangeTo:)` to modify proximity.
162185
///
163-
/// This method may only be called once, before any manager was created.
164-
/// - Parameter peripherals: Peripherals that are not connected.
186+
/// This method may only be called before any central manager was created
187+
/// or when Bluetooth state is `.poweredOff`. Existing list of peripherals
188+
/// will be overritten.
189+
/// - Parameter peripherals: Peripherals specifications.
165190
public static func simulatePeripherals(_ peripherals: [CBMPeripheralSpec]) {
166-
guard managers.isEmpty, CBMCentralManagerMock.peripherals.isEmpty else {
167-
NSLog("Warning: Peripherals can be added to simulation only once, and not after any central manager was initiated")
191+
guard managers.isEmpty || managerState == .poweredOff else {
192+
NSLog("Warning: Peripherals can not be added while the simulation is running. " +
193+
"Add peripherals before getting any central manager instance, " +
194+
"or when manager is powered off.")
168195
return
169196
}
170197
CBMCentralManagerMock.peripherals = peripherals

Example/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
PODS:
2-
- CoreBluetoothMock (0.10.0)
2+
- CoreBluetoothMock (0.11.0)
33

44
DEPENDENCIES:
55
- CoreBluetoothMock (from `../`)
@@ -9,7 +9,7 @@ EXTERNAL SOURCES:
99
:path: "../"
1010

1111
SPEC CHECKSUMS:
12-
CoreBluetoothMock: 4db6f2f85f268596cb05595d0ccc338212578e93
12+
CoreBluetoothMock: 158792f5f41671d5c61b882c38e804f35e0b9339
1313

1414
PODFILE CHECKSUM: bfd9fc7193b211c18bbe632884c30bc5d6a4807c
1515

Example/Pods/Local Podspecs/CoreBluetoothMock.podspec.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Example/Pods/Manifest.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Example/Pods/Target Support Files/CoreBluetoothMock/CoreBluetoothMock-Info.plist

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Example/Tests/NormalBehaviorTest.swift

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,26 @@ import XCTest
3232
@testable import nRF_Blinky
3333
@testable import CoreBluetoothMock
3434

35+
/// This test simulates normal behavior of a device with Nordic LED Button service.
36+
///
37+
/// It is using the app and testing it by sending notifications that trigger different
38+
/// actions.
3539
class NormalBehaviorTest: XCTestCase {
3640

3741
override func setUp() {
38-
(UIApplication.shared.delegate as! AppDelegate).mockingEnabled = true
42+
// This method is called AFTER ScannerTableViewController.viewDidLoad()
43+
// where the BlinkyManager is instantiated. A separate mock manager
44+
// is not created in this test.
45+
// Initially mock Bluetooth adapter is powered Off.
3946
CBMCentralManagerMock.simulatePeripherals([blinky, hrm, thingy])
4047
CBMCentralManagerMock.simulateInitialState(.poweredOn)
4148
}
4249

4350
override func tearDown() {
51+
// We can't call CBMCentralManagerMock.tearDownSimulation() here.
52+
// That would invalidate the BlinkyManager in ScannerTableViewController.
53+
// The central manager must be reused, so let's just power mock off,
54+
// which will allow us to set different set of peripherals in another test.
4455
CBMCentralManagerMock.simulatePowerOff()
4556
}
4657

@@ -65,8 +76,12 @@ class NormalBehaviorTest: XCTestCase {
6576
found.fulfill()
6677
}
6778
wait(for: [found], timeout: 3)
68-
XCTAssertNotNil(target)
69-
79+
XCTAssertNotNil(target, "nRF Blinky not found. Make sure you run the test on a simulator.")
80+
if target == nil {
81+
// Going further would cause a crash.
82+
return
83+
}
84+
7085
// Select found device.
7186
Sim.post(.selectPeripheral(at: 0))
7287

Example/Tests/ResetTest.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,26 @@ import XCTest
3232
@testable import nRF_Blinky
3333
@testable import CoreBluetoothMock
3434

35+
/// This test simulates a device with Nordic LED Button service which gets reset during
36+
/// connection.
37+
///
38+
/// It is using the app and testing it by sending notifications that trigger different
39+
/// actions.
3540
class ResetTest: XCTestCase {
3641

3742
override func setUp() {
38-
(UIApplication.shared.delegate as! AppDelegate).mockingEnabled = true
43+
// This method is called AFTER ScannerTableViewController.viewDidLoad()
44+
// where the BlinkyManager is instantiated.
45+
// Initially mock Bluetooth adapter is powered Off.
3946
CBMCentralManagerMock.simulatePeripherals([blinky, hrm, thingy])
4047
CBMCentralManagerMock.simulatePowerOn()
4148
}
4249

4350
override func tearDown() {
51+
// We can't call CBMCentralManagerMock.tearDownSimulation() here.
52+
// That would invalidate the BlinkyManager in ScannerTableViewController.
53+
// The central manager must be reused, so let's just power mock off,
54+
// which will allow us to set different set of peripherals in another test.
4455
CBMCentralManagerMock.simulatePowerOff()
4556
}
4657

@@ -65,7 +76,11 @@ class ResetTest: XCTestCase {
6576
found.fulfill()
6677
}
6778
wait(for: [found], timeout: 3)
68-
XCTAssertNotNil(target)
79+
XCTAssertNotNil(target, "nRF Blinky not found. Make sure you run the test on a simulator.")
80+
if target == nil {
81+
// Going further would cause a crash.
82+
return
83+
}
6984

7085
// Select found device.
7186
Sim.post(.selectPeripheral(at: 0))

README.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@ forwarded to their native equivalents, but on a simulator a mock implementation
2727

2828
## How to start
2929

30-
The *Core Bluetooth Mock* library is available only in Swift, and compatible with iOS 8.0+. For projects running Objective-C
31-
we recommend https://github.com/Rightpoint/RZBluetooth library.
30+
The *Core Bluetooth Mock* library is available only in Swift, and compatible with iOS 8.0+, macOS 10.13+, tvOS 9.0+ and watchOS 2.0+,
31+
with some features available only on newer platforms.
32+
For projects running Objective-C we recommend https://github.com/Rightpoint/RZBluetooth library.
3233

3334
### Including the library
3435

35-
The library support CocoaPods, Carthage and Swift Package Manager.
36+
The library support [CocoaPods](https://github.com/CocoaPods/CocoaPods), [Carthage](https://github.com/Carthage/Carthage) and
37+
[Swift Package Manager](https://swift.org/package-manager).
3638

3739
#### CocoaPods
3840

@@ -141,7 +143,10 @@ any central manager instance was created. It defines the intial state of the moc
141143
`CBMCentralManager.simulatePowerOff()` - turns off the mock central manager. All scans and connections will be terminated.
142144
143145
`CBMCentralManagerMock.simulatePeripherals(_ peripherals: [CBMPeripheralSpec])` - defines list of
144-
mock peripheral. This method should be called once, before any central manager was initialized.
146+
mock peripheral. This method should be called when the manager is powered off, or before any central manager was initialized.
147+
148+
`CBMCentralManagerMock.tearDownSimulation()` - sets the state of all currently existing central managers to `.unknown` and
149+
clears the list of managers and peripherals bringing the mock manager to initial state.
145150
146151
See [AppDelegate.swift](Example/nRFBlinky/AppDelegate.swift#L48) for reference. In the sample app the mock implementation is
147152
used only in UI Tests, which lauch the app with `mocking-enabled` parameter (see [here](Example/UI%20Tests/UITests.swift#L42)),
@@ -179,7 +184,7 @@ with `CBMCentralManagerOptionRestoreIdentifierKey` option. The map returned will
179184
`centralManager(:willRestoreState:)` callback in central manager's delegate.
180185

181186
`CBMCentralManagerFactory.simulateFeaturesSupport` - this closure will be used to emulate Bluetooth features supported
182-
by the manager. It is availalbe on iOS 13+.
187+
by the manager. It is availalbe on iOS 13+, tvOS 13+ or watchOS 6+.
183188

184189
## Sample application: nRF BLINKY
185190

0 commit comments

Comments
 (0)