I was writing a unit test for some code I was working on in an iOS App and ran into very strange behavior.
The PassthroughSubject sink closure in my code wasn't running and thus the test was failing. When i uncomment the print line in the below code it passes. It also passes with no problems in a playground. I tried the whole quite Xcode delete derived data trick and that didn't work. I tried to move the class and unit test to a completely different project and it was still failing. To me this looks like an Apple bug but wanted to ask here to see if anyone has any insights. see the below code. Note i've simplified the code from my original project a bit to make is easier to read. This makes me feel like RxSwift is more polished than Combine at this point. Never saw anything like this happen with RxSwift.
class BadPassThroughSendClass {
let publisher = PassthroughSubject<Any, Never>()
var anyCancellable: AnyCancellable?
var sinkClosureBlock: (()->())?
init() {
createSubscription()
}
func createSubscription() {
let subscription = publisher
.subscribe(on: DispatchQueue.global())
.receive(on: DispatchQueue.global())
.sink { [unowned self] event in
sinkClosureBlock?()
}
anyCancellable = AnyCancellable(subscription)
}
}
class UnitTests: XCTestCase {
func testBadPassThroughClass() {
let badClass = BadPassThroughSendClass()
let sinkExpectation = expectation(description: "Sink closure should fire")
badClass.sinkClosureBlock = {
sinkExpectation.fulfill()
}
// print("pass the test with this line")
badClass.publisher.send("my test message")
waitForExpectations(timeout: 0.5)
}
}
Related
This question already has answers here:
How To UnitTest Combine Cancellables?
(2 answers)
Closed 1 year ago.
I want to test that some of my PassthroughSubjects are subscribed to on init of my class
my class init is simple:
private let viewType = PassthroughSubject<ViewType, Never>()
init() {
setupObservers()
}
func setupObservers() {
viewType
.sink(
receiveValue: { [weak self] viewType in
guard let self = self else {
return
}
self.currentViewType = viewType
self.titleText.send(viewType.title)
self.hideXButton.send(viewType.shouldHideXButton)
self.reloadTableView.send()
})
.store(in: &publishers)
}
How can I test that viewType has been subscribed to?
Add a "print" operator into the middle of your sequence:
func setupObservers() {
viewType
.print() /* <<< -- Print debugging information*/
.sink(
receiveValue: { [weak self] viewType in
/* Your other code here */
It will print debugging information about the values going through the sequence including when a subscriber subscribes to it.
Obviously you should only use this at development time.
In terms of an XCTest you ask how to test if your publisher was subscribed to. The .sink operator is inline in your code and would do the subscription so it's not clear what you are trying to verify with an XCTest. You could capture the cancellation return in a variable, store it in your publishers set and validate that the cancellation is in the set as a means of ensuring your code was called. But since your subscription is intrinsic to the code it should always be completed so long as the code runs.
Say we have this enum
enum Action: String {
case doThing
case doOtherThing
}
This enum is used this way:
func run(action: Action, block: () -> Void)
Now, I unit test the run method so I need to pass an Action this way:
func testActionRun() {
let expect = expectation(description: #function)
let sut = ActionRunner()
sut.run(action: .doThing) {
expect.fulfill()
// Assert something
}
waitForExpectations(timeout: 0.1, handler: nil)
}
As I need to test other situations on ActionRunner, I ended with a lot of .doThing spread over the whole test suite.
The problem is: if I make a change in production code and change case doThing to case doThatThing now all my test suite fails because there is no a case doThing.
The perfect thing would be to declare a dummy case in test code to allow something like
sut.run(action: .dummyAction) {
}
but enum does not allow that as it doesn't allows inheritance nor a extension to add a case.
The first option that came to my mind was to convert Action into a protocol, but that change is unnecessary in production and its only purpose is to accomplish something in test code.
So, is it there another option to achieve this?
The question of how to avoid coupling when using enums is a tricky one. I bumped into that myself a few times with no solid answer :/
One point you raise is the one of using a protocol, and that feels unnecessary in production. I sort of agree with that, but most time it's the necessary evil.
In the example you showed though I think maybe a tweak in the design might solve part of the problem.
In particular when looking at this code
func run(action: Action, block: () -> Void) {
// ...
}
func testActionRun() {
let expect = expectation(description: #function)
let sut = ActionRunner()
sut.run(action: .doThing) {
expect.fulfill()
// Assert something
}
waitForExpectations(timeout: 0.1, handler: nil)
}
What comes to mind to me is that your Action specifies a certain behaviour. That is when you test the run method passing .doThing you expect a different behaviour than when passing .doOtherThing.
If that's right, is there any reason why you need to pass the action enum instance and an action block to the run function?
You could separate the code that defines the behaviour from the one performs the actual action even more that what you've done already. For example:
protocol Actionable {
var action: () -> () { get }
}
enum Action: Actionable {
case doThing
case doOtherThing
var action {
switch self {
case .doThing: return ...
case .doOtherThing: return ...
}
}
class ActionRunner {
func run(actionable: Actionable) {
actionable.action()
}
}
func testActionRun() {
let expect = expectation(description: #function)
let sut = ActionRunner()
sut.run(actionable: FakeActionable()) {
expectation.fulfill()
}
waitForExpectations(timeout: 0.1, handler: nil)
}
class FakeActionable: Actionable {
let action = { }
}
func testDoThing() {
let sut = Action.doThing
sut.action()
// XCTAssert for the expected effect of the action
}
Note: I haven't actually compiled that code, so bear with me if it has some mistakes. It should give the idea though.
This way you have ActionRunner which only purpose is to properly run a given Actionable, and the Action enum which only purpose is to describe what different actions should do.
This example code is rather restrict in what it can do, only run () -> () actions, but you could build on top of it to achieve more advanced behaviours.
If you change your production code you have to change your test code too in order to test those new changes.
Maybe you can set the value on an Action variable in the setUp func of your XCTestCase class
import XCTest
class SharingKitTests: XCTestCase {
var theAction: Action!
override func setUp() {
super.setUp()
self.theAction = .doThing
}
}
Then you will be able to use this theAction var in all your test methods, and if you need to change the value you only need to change it in one place.
I am working on testing an API through Alamofire. I need to make a single call to the server to prepare it for the integration test. Once that is done, I am ready to start running tests.
The usual override setUp() is run for every test, so I do not want to do that.
I have therefore chosen to override the class setUp() as described here: https://developer.apple.com/reference/xctest/xctestcase
That's all well and good, but now, I no longer can use the standard waitForExpectations. (In the class override setUp()) I get several compiler errors that tell me that I am no longer calling the same waitForExpectations because I am in a class method, not a test case.
To try to get around this, I wanted to use a semaphore like so:
class ServiceLayerTests: XCTestCase {
static var apiService: APIService = APIService()
let sessionManager = SessionManager(serverTrustPolicyManager: ServerTrustPolicyManager(policies: ["http://localhost:3000/": .disableEvaluation]))
static var companyManger: UserWebResource?
static var companyManagerID = -1
override class func setUp() {
apiService.baseURL = "http://localhost:3000/"
beginIntegrationTests()
}
class func beginIntegrationTests() {
var semaphore = DispatchSemaphore(value: 0)
apiService.beginIntegrationTests(completion: {resource, error in
if let resource = resource {
if let manager = resource as? UserWebResource {
companyManger = manager
companyManagerID = manager.id
semaphore.signal()
}
}
})
_ = semaphore.wait(timeout: DispatchTime.distantFuture)
}
}
This does not work. Under the hood, there is an alamo fire call to the server and it responds with the user to use for the integration tests. I do see the server spinning, so I know that the actual communication is happening, but I never get into the completion closure.
I suspect I am not understanding how Swift does semaphores and that I have created a deadlock somehow. If somebody has a better solution, I'd be more than happy to hear it.
I get several compiler errors that tell me that I am no longer calling
the same waitForExpectations because I am in a class method, not a
test case
That makes sense. What you probably want is to refactor so that you are in a test case:
override class func setUp() {
apiService.baseURL = "http://localhost:3000/"
}
func testIntegrationTests() {
let urlExpectation = expectation(description: "INTEGRATION TEST")
apiService.beginIntegrationTests(completion: {resource, error in
// ...
urlExpectation.fulfill()
})
// not sure what an acceptable timeout would be, I chose this at random
waitForExpectations(timeout: 25) { error in
if let error = error {
print("Error: \(error.localizedDescription)")
}
}
}
One of the best resources with some good test examples can be found here: http://nshipster.com/xctestcase/
You can create the expectation as a lazy var that executes your one-time set up and fulfills on completion.
At the beginning of your per-test setUp() function you can wait for that expectation.
None of your tests will run until it is fulfilled, and the initial setup will run only once.
class WaitForSetup_Tests: XCTestCase {
lazy var initialSetupFinished: XCTestExpectation = {
let initialSetupFinished = expectation(description: "initial setup finished")
initialSetupTask() { // your setup task that executes this closure on completion
initialSetupFinished.fulfill()
return
}
return initialSetupFinished
}()
override func setUp() {
wait(for: [initialSignOutFinished], timeout: 2.0)
// do your per-test setup here
}
}
Note: This solution avoids using the override class function setUp() class method, because I couldn't figure out how to use the expectations except for in an instance.
I've inherited some Swift 3 code which uses RxSwift to manage a store. The basic layout of the class is:
class UserActivityStore {
fileprivate lazy var internalDataCache: Variable<Set<NSUserActivity>?> = Variable(nil)
func addAction(_ actionToAdd: NSUserActivity) {
var content = internalDataCache.value ?? Set<NSUserActivity>()
content.insert(actionToAdd)
internalDataCache.value = content
}
func resolveAction(_ action: NSUserActivity) {
var content = internalDataCache.value
_ = content?.remove(action)
internalDataCache.value = content
}
func expectActions(_ filter: #escaping ((NSUserActivity) -> Bool)) -> Observable<NSUserActivity> {
let observable = Observable<NSUserActivity>.create { (observer) -> Disposable in
return self.internalDataCache.asObservable().subscribeNext { (newActions) in
guard let newActions = newActions else { return }
for eachAction in newActions where filter(eachAction) {
observer.onNext(eachAction)
self.resolveAction(eachAction)
}
}
}
return observable
}
}
When an action is added to this, it adds the item to the set correctly. However, the observer (in expectActions) catches that change and resolves it. Since this is all in a single thread, the error "Warning: Recursive call or synchronization error!" is triggered.
I think this is a perfectly legitimate error and that RxSwift is probably correct in its handling. However, I can't help thinking that this is a bad model. The code is essentially handling a queue of NSUserActivity objects itself.
Is this genuinely a modelling error / abuse of RxSwift or is my limited understanding of RxSwift misunderstanding this? As a hacky solution, I've tried replacing the resolveAction function with a single line internalDataCache.value?.remove(action) but that still triggers the observable and hence the bug.
Changing the observable to use a different queue (Serial or Concurrent dispatch) fixes the problem but I'm not convinced its the correct fix.
I'm using the DEVELOPMENT-SNAPSHOT-2016-06-06-a version of Swift. I cannot seem to get around this issue, I've tried using #noescape in various places, but I still have the following error:
Closure cannot implicitly capture a mutating self parameter
To better explain, here is a simple example:
public struct ExampleStruct {
let connectQueue = dispatch_queue_create("connectQueue", nil)
var test = 10
mutating func example() {
if let connectQueue = self.connectQueue {
dispatch_sync(connectQueue) {
self.test = 20 // error happens here
}
}
}
}
Something must have changed in these Swift binaries that is now causing my previously working code to break. A workaround I want to avoid is making my struct a class, which does help in fixing the issue. Let me know if there is another way.
I cannot test it, because I'm not using a build with that error, but I'm pretty sure by capturing self explicitly you can fix it:
dispatch_sync(connectQueue) { [self] in
self.test = 20
}
EDIT: Apparently it doesn't work, maybe you can try this (not very nice tbh):
var copy = self
dispatch_sync(connectQueue) {
copy.test = 20
}
self = copy
If you want to read more on why, here is the responsible Swift proposal.
The new dispatch API makes the sync method #noreturn so you wouldn't need the explicit capture:
connectQueue.sync {
test = 20
}
You are using Swift3 since you mentioned a recent dev snapshot of Swift. Try below and let me know if it works:
public struct ExampleStruct {
let connectQueue = DispatchQueue(label: "connectQueue", attributes: .concurrent)//This creates a concurrent Queue
var test = 10
mutating func example() {
connectQueue.sync {
self.test = 20
}
}
}
If you are interested in other types of queues, check these:
let serialQueue = DispatchQueue(label: "YOUR_QUEUE", attributes: .serial)
serialQueue.sync {
//
}
Get the mainQueue asynchronously and synchronously:
DispatchQueue.main.async {
//async operations
}
DispatchQueue.main.sync {
//sync operations
}
And if you are interested in Background:
DispatchQueue.global(attributes: .qosDefault).async {
//async operations
}
You could refer this for new features in Swift3 and for changes to existing version: Migrating to Swift 2.3 or Swift 3 from Swift 2.2