Test Doubles in Swift – Dummy, Stub, Fake, Mock and Spy

When writing a Unit test for a piece of code, we need to isolate this piece of code from all other dependencies it has. These dependencies are objects of other classes that do not need to be tested in this particular unit test. For example, a piece of code we are unit testing might depend on an HTTPClient object that is being used to send HTTP Request and load some data from a backend web service.

Another example of a dependency could be a Validator object that is being used to validate the username or the password provided. If in the unit test we are writing,  the functionality of HTTPClient or code inside of a Validator class does not need to be tested, then these dependencies can be faked and presented as fake objects. These fake objects are called Test Doubles and exist in different forms for different purposes. These Test Doubles are Dummy, Stub, Fake, Mock and a SpyIn this tutorial, I will attempt to describe each of these with a little code snippet as an example.

Fake Object

The first Test Double I would like to begin with is called a Fake Object. A Fake object will need to conform to the same protocol that the real object does but the fake object does not need to behave 100% as a real object does. It will work as a real object but not entirely. The fake object will take a shortcut and behave in a much simpler way.

An example of a Fake object can be an instance of a database class. Instead of actually persisting data to permanent storage, it will keep data in memory and will respond with a confirmation that the data is stored. Instead of reading data from a database it will read data from a JSON file and return the result as if it was read from a database.

Dummy Object

Think of a dummy object as it is a placeholder. Some times we need to call a function with a set of parameters. One or more of these parameters might be required to be provided but their value might be irrelevant or not even used. So, in those cases when we are required to provide an object but we know that the function call is fake and the values that this object contains are not going to be even used, we can create and provide a Dummy object instead of a real one.

For example, let’s assume we have the following Swift structure which is a model of the user registration form.

struct SignupFormModelDummy: SignupFormModelProtocol {
    var firstName = "abc"
    var lastName = "abc"
    var email = "abc"
    var password = "abc"
    var repeatPassword = "abc"
}

Now when we have dummy struct we can use its object as a parameter to a fake function which we know will not use any of the provided user details. We just needed an object to satisfy the requirements of a required parameter of a SignupFormModel data type.

 

let mockSignup = MockSignupWebService();

mockSignup.signupUser(SignupFormModelDummy())
mockSignup.signupUser(SignupFormModelDummy())
mockSignup.signupUser(SignupFormModelDummy())

Dummy objects can also be used as fillers to Collection types.

 

var dummyFormObjects: [SignupFormModelProtocol] = []

dummyFormObjects.append(SignupFormModelDummy())
dummyFormObjects.append(SignupFormModelDummy())
dummyFormObjects.append(SignupFormModelDummy())
dummyFormObjects.append(SignupFormModelDummy())
dummyFormObjects.append(SignupFormModelDummy())

fakeSignupService.checkInGroupOfUsers(dummyFormObjects) 

Stub

Stub object is very similar to the dummy objects, except that its data can be used or validated. The purpose of the Stub object is to make your system behave a certain way depending on the value the stub object contains. For example, you can use a Stub object to create a User Details object that has an invalid email address. Using a Stub object with an invalid user address will always make your system/API respond with a specific response.

Stubs can be a Model structure and it can be a Service class or a function that always responds with the same return value.

Let’s have a look at an example of a Stub Model struct. The data this Stub object contains will be validated but it will always make the service class respond with an error message that email address has an invalid format.

struct InvalidSignupFormModelStub: Codable {
    let firstName = "Sergey"
    let lastName = "Kargopolov"
    let email = "[email protected]"
    let password = "12345678"
}


The following class has function stubs. It functions always returned predefined data no matter what input parameters they get.

class ValidSignupFormModelValidatorStub {
    
    func isFirstNameValid(firstName: String) -> Bool {  
        return true
    }
    
    func isLastNameValid(lastName: String) -> Bool {
        return true
    }
    
    func isValidEmailFormat(email: String) -> Bool {
       return true
    }
    
    func isPasswordValid(password: String) -> Bool {
        return true
    }
    
    func doPasswordsMatch(password: String, repeatPassword: String) -> Bool {
        true
    } 
 
}

Mock Objects

Mock objects are similar to Fake objects except that Mock objects might not have even a simpler implementation of the requested functionality. If a Fake object attempts to provide a simpler implementation and works with in-memory storage instead of a real database, the Mock object will not do even that. A mock object will simply record the fact that it was used, how many times was used and will return back a predefined result. So, we use Mock objects if we need to know that a specific method was called, how many times it was called and if need this mock method to return something back as a return value, the return value will be hard-coded and predefined.

Below is an example of a Mock class that does not perform any validation, does not attempt to store user details in memory, or store it in a temporary location. It simply registers the fact that the processUserSignup() method was called and counts the number of times the method was called.

@testable import PhotoApp

class MockSignupWebService: SignupWebServiceProtocol {
 
    var processUserSignupCalled = false
    var processUserSignupCalledNumberOfTimes = 0
 
    func processUserSignup(formModel: SignupFormModel) {
       processUserSignupCalled = true
       processUserSignupCalledNumberOfTimes += 1
    }
 
}

 

I hope this tutorial was of some value to you. I will be sharing more code examples on Unit Testing with Swift, so stay tuned!

Happy learning! 🙋🏻‍♂️

Leave a Reply

Your email address will not be published. Required fields are marked *