Skip to content

Commit

Permalink
Merge pull request #21 from NordicSemiconductor/develop
Browse files Browse the repository at this point in the history
Version 0.11.0
  • Loading branch information
philips77 authored Aug 6, 2020
2 parents 63cde25 + f218834 commit b9baa3a
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 37 deletions.
2 changes: 1 addition & 1 deletion CoreBluetoothMock.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'CoreBluetoothMock'
s.version = '0.10.0'
s.version = '0.11.0'
s.summary = 'Mocking library for CoreBluetooth.'

s.description = <<-DESC
Expand Down
15 changes: 7 additions & 8 deletions CoreBluetoothMock/Classes/CBMCentralManagerFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

import CoreBluetooth

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

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

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

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

}
49 changes: 38 additions & 11 deletions CoreBluetoothMock/Classes/CBMCentralManagerMock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,13 @@ public class CBMCentralManagerMock: NSObject, CBMCentralManager {
/// A flag set to true few milliseconds after the manager is created.
/// Some features, like the state or retrieving peripherals are not
/// available when manager hasn't been initialized yet.
private var initialized: Bool = false
private var initialized: Bool {
// This method returns true if the manager is added to
// the list of managers.
// Calling tearDownSimulation() will remove all managers
// from that list, making them uninitialized again.
return CBMCentralManagerMock.managers.contains { $0.ref == self }
}

// MARK: - Initializers

Expand Down Expand Up @@ -130,24 +136,41 @@ public class CBMCentralManagerMock: NSObject, CBMCentralManager {
}

private func initialize() {
if CBMCentralManagerMock.peripherals.isEmpty {
NSLog("Warning: No simulated peripherals. Call simulatePeripherals(:) before creating central manager")
if CBMCentralManagerMock.managerState == .poweredOn &&
CBMCentralManagerMock.peripherals.isEmpty {
NSLog("Warning: No simulated peripherals. " +
"Call simulatePeripherals(:) before creating central manager")
}
// Let's say initialization takes 10 ms. Less or more.
queue.asyncAfter(deadline: .now() + .milliseconds(10)) { [weak self] in
if let self = self {
CBMCentralManagerMock.managers.append(WeakRef(self))
self.initialized = true
self.delegate?.centralManagerDidUpdateState(self)
}
}
}

/// Removes all active central manager instances and peripherals from the
/// simulation, resetting it to the initial state.
///
/// Use this to tear down your mocks between tests, e.g. in `tearDownWithError()`.
/// All manager delegates will receive a `.unknown` state update.
public static func tearDownSimulation() {
// Set the state of all currently existing cenral manager instances to
// .unknown, which will make them invalid.
managerState = .unknown
// Remove all central manager instances.
managers.removeAll()
// Set the manager state to powered Off.
managerState = .poweredOff
peripherals.removeAll()
}

// MARK: - Central manager simulation methods

/// Sets the initial state of the Bluetooth central manager.
///
/// This method should only be called ones, before any `CBMCentralManagerMock`
/// This method should only be called ones, before any central manager
/// is created. By default, the initial state is `.poweredOff`.
/// - Parameter state: The initial state of the central manager.
public static func simulateInitialState(_ state: CBMManagerState) {
Expand All @@ -157,14 +180,18 @@ public class CBMCentralManagerMock: NSObject, CBMCentralManager {
/// This method sets a list of simulated peripherals.
///
/// Peripherals added using this method will be available for scanning
/// and connecting, depending on their proximity. Use
/// peripheral's `simulateProximity(of:didChangeTo:)` to modify proximity.
/// and connecting, depending on their proximity. Use peripheral's
/// `simulateProximity(of:didChangeTo:)` to modify proximity.
///
/// This method may only be called once, before any manager was created.
/// - Parameter peripherals: Peripherals that are not connected.
/// This method may only be called before any central manager was created
/// or when Bluetooth state is `.poweredOff`. Existing list of peripherals
/// will be overritten.
/// - Parameter peripherals: Peripherals specifications.
public static func simulatePeripherals(_ peripherals: [CBMPeripheralSpec]) {
guard managers.isEmpty, CBMCentralManagerMock.peripherals.isEmpty else {
NSLog("Warning: Peripherals can be added to simulation only once, and not after any central manager was initiated")
guard managers.isEmpty || managerState == .poweredOff else {
NSLog("Warning: Peripherals can not be added while the simulation is running. " +
"Add peripherals before getting any central manager instance, " +
"or when manager is powered off.")
return
}
CBMCentralManagerMock.peripherals = peripherals
Expand Down
4 changes: 2 additions & 2 deletions Example/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
PODS:
- CoreBluetoothMock (0.10.0)
- CoreBluetoothMock (0.11.0)

DEPENDENCIES:
- CoreBluetoothMock (from `../`)
Expand All @@ -9,7 +9,7 @@ EXTERNAL SOURCES:
:path: "../"

SPEC CHECKSUMS:
CoreBluetoothMock: 4db6f2f85f268596cb05595d0ccc338212578e93
CoreBluetoothMock: 158792f5f41671d5c61b882c38e804f35e0b9339

PODFILE CHECKSUM: bfd9fc7193b211c18bbe632884c30bc5d6a4807c

Expand Down
4 changes: 2 additions & 2 deletions Example/Pods/Local Podspecs/CoreBluetoothMock.podspec.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Example/Pods/Manifest.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 18 additions & 3 deletions Example/Tests/NormalBehaviorTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,26 @@ import XCTest
@testable import nRF_Blinky
@testable import CoreBluetoothMock

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

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

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

Expand All @@ -65,8 +76,12 @@ class NormalBehaviorTest: XCTestCase {
found.fulfill()
}
wait(for: [found], timeout: 3)
XCTAssertNotNil(target)

XCTAssertNotNil(target, "nRF Blinky not found. Make sure you run the test on a simulator.")
if target == nil {
// Going further would cause a crash.
return
}

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

Expand Down
19 changes: 17 additions & 2 deletions Example/Tests/ResetTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,26 @@ import XCTest
@testable import nRF_Blinky
@testable import CoreBluetoothMock

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

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

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

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

// Select found device.
Sim.post(.selectPeripheral(at: 0))
Expand Down
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ forwarded to their native equivalents, but on a simulator a mock implementation

## How to start

The *Core Bluetooth Mock* library is available only in Swift, and compatible with iOS 8.0+. For projects running Objective-C
we recommend https://github.com/Rightpoint/RZBluetooth library.
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+,
with some features available only on newer platforms.
For projects running Objective-C we recommend https://github.com/Rightpoint/RZBluetooth library.

### Including the library

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

#### CocoaPods

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

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

## Sample application: nRF BLINKY

Expand Down

0 comments on commit b9baa3a

Please sign in to comment.