filestack/filestack-swift

View on GitHub
Tests/FilestackSDKTests/BaseOperationTests.swift

Summary

Maintainability
C
1 day
Test Coverage
//
//  BaseOperationTests.swift
//  FilestackSDKTests
//
//  Created by Ruben Nine on 04/07/2020.
//  Copyright © 2020 Filestack. All rights reserved.
//

import XCTest
@testable import FilestackSDK

class WorkOperation<R>: BaseOperation<R> {
    let block: ((WorkOperation) -> Void)

    required init(block: @escaping ((WorkOperation) -> Void)) {
        self.block = block
        super.init()
    }

    override func main() {
        block(self)
    }
}

func serialOperationQueue() -> OperationQueue {
    let queue = OperationQueue()

    queue.maxConcurrentOperationCount = 1

    return queue
}

class BaseOperationTests: XCTestCase {
    func testReadyOperation() throws {
        let operation = WorkOperation<Bool>() { _ in }

        // Assert flags
        XCTAssertEqual(operation.isReady, true)
        XCTAssertEqual(operation.isFinished, false)
        XCTAssertEqual(operation.isExecuting, false)
        XCTAssertEqual(operation.isCancelled, false)
    }

    func testFinishingOperation() throws {
        let queue = serialOperationQueue()
        let semaphore = DispatchSemaphore(value: 0)

        let operation = WorkOperation<Bool>() { work in
            Thread.sleep(forTimeInterval: 2)
            work.finish(with: .success(true))
            semaphore.signal()
        }

        // Enqueue operation and wait for task completion.
        queue.addOperation(operation)
        semaphore.wait()

        // Assert flags
        XCTAssertEqual(operation.isReady, true)
        XCTAssertEqual(operation.isExecuting, false)
        XCTAssertEqual(operation.isFinished, true)
        XCTAssertEqual(operation.isCancelled, false)

        // Assert that we got the expected `result`.
        switch operation.result {
        case let .success(result):
            XCTAssertEqual(result, true)
        case .failure(_):
            XCTFail()
        }
    }

    func testCancellingStartedOperation() throws {
        let queue = serialOperationQueue()
        let semaphore = DispatchSemaphore(value: 0)

        let operation = WorkOperation<Bool>() { work in
            Thread.sleep(forTimeInterval: 2)
            work.finish(with: .success(true))
        }

        // Assert that operation enters `executing` state after it is added to the queue.
        queue.addOperation(operation)
        _ = semaphore.wait(timeout: .now() + .milliseconds(500))

        // Assert flags
        XCTAssertEqual(operation.isReady, true)
        XCTAssertEqual(operation.isExecuting, true)
        XCTAssertEqual(operation.isFinished, false)
        XCTAssertEqual(operation.isCancelled, false)

        // Cancel ongoing operation.
        operation.cancel()

        // Assert flags
        XCTAssertEqual(operation.isReady, true)
        XCTAssertEqual(operation.isExecuting, false)
        XCTAssertEqual(operation.isFinished, true)
        XCTAssertEqual(operation.isCancelled, true)

        // Assert that we got the expected `result`.
        switch operation.result {
        case .success(_):
            XCTFail()
        case let .failure(error):
            XCTAssertEqual(error.localizedDescription, Error.cancelled.localizedDescription)
        }
    }

    func testCancellingNotStartedOperation() throws {
        let operation = WorkOperation<Bool>() { _ in }

        // Cancel ongoing operation.
        operation.cancel()

        // Assert flags
        XCTAssertEqual(operation.isReady, true)
        XCTAssertEqual(operation.isFinished, true)
        XCTAssertEqual(operation.isExecuting, false)
        XCTAssertEqual(operation.isCancelled, true)

        switch operation.result {
        case .success(_):
            XCTFail()
        case let .failure(error):
            XCTAssertEqual(error.localizedDescription, Error.cancelled.localizedDescription)
        }
    }

    func testObserversOnCancelledOperation() throws {
        let queue = serialOperationQueue()
        let semaphore = DispatchSemaphore(value: 0)

        let operation = WorkOperation<Bool>() { work in
            Thread.sleep(forTimeInterval: 5)
            work.finish(with: .success(true))
        }

        let isExecutingExpectation = self.expectation(description: "should change `isExecuting` to true")
        let isFinishedExpectation = self.expectation(description: "should change `isFinished` to true")
        let isCancelledExpectation = self.expectation(description: "should change `isCancelled` to true")

        var observers: [NSKeyValueObservation] = []

        observers.append(operation.observe(\.isExecuting, options: [.new]) { (operation, change) in
            if change.newValue == true {
                isExecutingExpectation.fulfill()
            }
        })

        observers.append(operation.observe(\.isFinished, options: [.new]) { (operation, change) in
            if change.newValue == true {
                isFinishedExpectation.fulfill()
            }
        })

        observers.append(operation.observe(\.isCancelled, options: [.new]) { (operation, change) in
            if change.newValue == true {
                isCancelledExpectation.fulfill()
            }
        })

        queue.addOperation(operation)
        _ = semaphore.wait(timeout: .now() + .milliseconds(500))

        // Cancel ongoing operation.
        operation.cancel()

        waitForExpectations(timeout: 15, handler: nil)
    }

    func testObserversOnFinishingOperation() throws {
        let queue = serialOperationQueue()

        let operation = WorkOperation<Bool>() { work in
            Thread.sleep(forTimeInterval: 1)
            work.finish(with: .success(true))
        }

        let isExecutingExpectation = self.expectation(description: "should change `isExecuting` to true")
        let isFinishedExpectation = self.expectation(description: "should change `isFinished` to true")

        var observers: [NSKeyValueObservation] = []

        observers.append(operation.observe(\.isExecuting, options: [.new]) { (operation, change) in
            if change.newValue == true {
                isExecutingExpectation.fulfill()
            }
        })

        observers.append(operation.observe(\.isFinished, options: [.new]) { (operation, change) in
            if change.newValue == true {
                isFinishedExpectation.fulfill()
            }
        })

        queue.addOperation(operation)

        waitForExpectations(timeout: 15, handler: nil)
    }
}