Tony Stark Tony Stark - 1 year ago 65
Swift Question

How to test controller class in MVC?

I'm building a simple iOS app in Swift according to the Model-View-Controller pattern. I can test the Model class by giving it input data and asserting the output result against my expectation. But I was wondering how I could test the Controller class? It seems that if I do want to test the Controller class, the testing logic would be much more complicated. Is there a standard way of testing the Controller class? Thanks in advance for any help!

Answer Source

Don't test your UIViewControllers. There is a lot of stuff that happens to them that you don't see and/or have no control over. Instead, keep as much logic in other objects such as View Models, and not your UIViewControllers. You can then test your View Models, just like you would test your Models.


How you might want to structure your UIViewControllers and test Models & View Models:

The main takeaway from this code is:

  1. Use Dependency Injection
  2. Give real dependencies to classes in Release, and fake dependencies in tests

View Controller

// this class is super simple, so there's hardly any reason to test it now.
class SomeViewController: UIViewController {
    @IBOutlet weak var someLabel: UILabel!

    override func viewDidLoad() {

        // we give the *real* user accessor in the view controller
        let viewModel = SomeViewModel(userAccessor: DBUserAccessor())
        someLabel.text = viewModel.welcomeMessage

User Model

struct User {
    var name: String

User Accessor

This is some dependency that your View Model needs. Give a real one during Release (in your View Controller). Give a fake one during tests, so that you can control it.

protocol UserAccessor {
    var currentUser: User? { get }

// since we're using the fake version of this class to test the view model, you might want to test this class on its own
// you would do that using the same principles that I've shown (dependency injection).
class DBUserAccessor: UserAccessor {
    var currentUser: User? {
        // some real implementation that's used in your app
        // get the user from the DB
        return User(name: "Samantha") // so not really this line, but something from CoreData, Realm, etc.

class FakeUserAccessor: UserAccessor {
    // some fake implementation that's used in your tests
    // set it to whatever you want your tests to "see" as the current User from the "DB"
    var currentUser: User?

View Model

This is where the actual logic lives that you want to test.

class SomeViewModel {
    let userAccessor: UserAccessor

    init(userAccessor: UserAccessor) {
        self.userAccessor = userAccessor

    var welcomeMessage: String {
        if let username = self.username {
            return "Welcome back, \(username)"
        } else {
            return "Hello there!"

    var username: String? {
        return userAccessor.currentUser?.name


And finally, how you want to test it.

class SomeViewModelTest: XCTestCase {
    func testWelcomeMessageWhenNotLoggedIn() {
        let userAccessor = FakeUserAccessor()
        let viewModel = SomeViewModel(userAccessor: userAccessor) // we give the *fake* user accessor to the view model in tests
        userAccessor.currentUser = nil // set the fake UserAccessor to not have a user "logged in"

        // assert that the view model, which uses whatever you gave it, gives the correct message
        XCTAssertEqual(viewModel.welcomeMessage, "Hello there!")

    func testWelcomeMessageWhenLoggedIn() {
        let userAccessor = FakeUserAccessor()
        let viewModel = SomeViewModel(userAccessor: userAccessor)
        userAccessor.currentUser = User(name: "Joe") // this time, the use is "logged in"

        XCTAssertEqual(viewModel.welcomeMessage, "Welcome back, Joe") // and we get the correct message
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download