'how to test view model protocol and UI elements of UIVIewController in iOS swift?

This is UIViewController Class below where the user will click on the button title "Run", which shows some progress on ProgressView after that random number is generated and displayed on View. Users can reset the flow again.

class ProgressViewController: UIViewController {
    
    @IBOutlet weak var actionButton: UIButton!
    @IBOutlet weak var progressView: UIProgressView!
    @IBOutlet weak var resultLabel: UILabel!
    var viewModel: ProgressViewModelProtocol!

    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }

    func configureUI() {
        setInitialStateOfElements()
    }
    
    func setInitialStateOfElements() {
        actionButton.isHidden = false
        actionButton.setTitle("Run", for: .normal)
        progressView.isHidden = true
        progressView.progress = 0
        resultLabel.isHidden = true
    }
    
    func setInProgressStateOfElements() {
        actionButton.isHidden = true
        progressView.isHidden = false
        resultLabel.isHidden = true
    }
    
    func setLoadedStateOfElements(randomValue: String) {
        actionButton.isHidden = false
        actionButton.setTitle("Reset", for: .normal)
        progressView.isHidden = true
        resultLabel.isHidden = false
        resultLabel.text = randomValue
    }
    
    @IBAction func actionButtonClicked() { 
        viewModel.handleButtonAction()
    }
}

extension ProgressViewController: ProgressViewProtocol {
    func displayTheProgressValue(randomValue: String) {
        self.setLoadedStateOfElements(randomValue: randomValue)
    }
    
    func updateProgressOnView(progress: Float) {
        self.setInProgressStateOfElements()
        self.progressView.setProgress(progress, animated: true)
    }
    
    func setTheInitialState() {
        self.setInitialStateOfElements()
    }
}

This is ProgressViewModel Class.

enum ProgressState {
    case Initial
    case InProgress
    case Loaded(randomValue: String)
}

protocol ProgressViewProtocol: AnyObject {
    func setTheInitialState()
    func updateProgressOnView(progress: Float)
    func displayTheProgressValue(randomValue: String)
}

protocol ProgressViewModelProtocol: AnyObject {
    func handleButtonAction()
}


class ProgressViewModel  {
    var state: ProgressState {
        didSet {
            switch state {
            case .Initial:
                view.setTheInitialState()
            case .InProgress:
                madeSomeProgress ()
            case .Loaded(let randomValue):
                view.displayTheProgressValue(randomValue: randomValue)
            }
        }
    }
    
    init(state: ProgressState) {
        self.state = state
    }
    weak var view: ProgressViewProtocol!
    weak var coordinator : ProgressCoordinator?

    func madeSomeProgress()  {
        let totalTime: TimeInterval = 2
        let stepTime: TimeInterval = 0.1
        let startDate = Date()
        let cancellable = Timer.publish(every: stepTime, on: .main, in: .default)
            .autoconnect()
            .map { Float($0.timeIntervalSince(startDate) / totalTime) }
            .sink {  [weak self] progressValue in
                self?.view.updateProgressOnView(progress: progressValue)
            }
        
        DispatchQueue.main.asyncAfter(deadline: .now() + totalTime) { [weak self] in
            cancellable.cancel()
            let randomValue = "\(Int.random(in: 1...99))"
            self?.state = ProgressState.Loaded(randomValue: randomValue)
        }
    }
}

extension ProgressViewModel: ProgressViewModelProtocol {
    func handleButtonAction() {
        switch state {
        case .Initial:
            state = .InProgress
        case .InProgress:
            print("in progress")
        case .Loaded:
            state = .Initial
        }
    }
}

This is ProgressViewModelTests class where I am facing issues while writing test cases for ProgressState.

import XCTest
@testable import MysteriousApp

class ProgressViewProtocolMock: ProgressViewProtocol {
    var randomValueReceived = ""
    func displayTheProgressValue(randomValue: String) {
        randomValueReceived = randomValue
    }
    
    func updateProgressOnView(progress: Float) {
        
    }
    
    func setTheInitialState() {
        
    }
}

class ProgressViewModelTests: XCTestCase {
    var viewModel: ProgressViewModel!
    var view: ProgressViewProtocolMock!
    
    override func setUp() {
        viewModel = ProgressViewModel(state: .Initial)
        view = ProgressViewProtocolMock()
        viewModel.view = view
    }
    
    func testInitialState() {
        viewModel.state = .Initial
    }

    func testInProgressState() {
        viewModel.state = .InProgress
    }
    func testLoadedState() {
        viewModel.state = .Loaded(randomValue: "56")
    }
}

enter image description here

enter image description here

enter image description here



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source