I think it’s unnecessary to start this article with how important it is to write tests — you know it better than me. Also, I won’t go to dive into the theory of patterns and approaches. My goal is just to show you how easy and quickly you can start writing tests today.
To avoid turning this article for the sake of the article itself, I will explain why I decided to prepare this small tutorial.
Recently I conducted several tech interviews and noticed one curious thing. More and more engineers (even middle and seniors) neglect writing tests. They were great candidates, but they didn’t write tests at all. So, let this article be here just for those who need to start writing tests right now.
Stay tuned.
In your existing project, we need to create a new target — Unit Testing Bundle.
Name your product. A target to be tested should be your app or framework.
If you don’t see your target in the list of targets, then we need to add it manually using the manage targets option.
When you open a file with the test, you will see XCTestCase
template with a few methods. We need only testExample()
. Let’s run it using a rectangle on the left side of this method.
You will see a green checkmark. Your console log will be filled with information about when the test started, how a lot it takes, and so on. But let’s open Test navigator using CMD + 6. You will see the hierarchy of tests and the same green checkmarks.
Green checkmarks mean — your tests are passed. Let’s make our own now.
You testExample()
method let’s add a following code and run it using triangle.
func testExample() throws {
let expr1 = "expression 1"
let expr2 = expr1
XCTAssertEqual(expr1, expr2, "Expressions are equal")
}
In this implementation, we used a specific assertion which is a part of XCTest
framework. Let’s try another example. For that, you can create testExample2()
.
func testExample2() throws {
let expr1 = "expression 1"
let expr2 = "expression 2"
XCTAssertNotEqual(expr1, expr2, "Expressions are not equal")
}
Since we have several tests, you would want to run them all. For that, you can use either the Test navigator or the class declaration line.
As you can see XCTest framework provides a lot of asserts for all your general test cases:
// Bool assert TRUE
XCTAssertTrue(expression, "description")
// Bool assert FALSE
XCTAssertFalse(expression, "description")
// Assert that expression is nil
XCTAssertNil(expression, "description")
// Assert that expression is NOT nil
XCTAssertNotNil(expression, "description")
// Assert that an expression is not nil and returns the unwrapped value
XCTUnwrap(expression, "description")
// Asserts that two expressons have the same value or not
XCTAssertEqual(expr1, expr2, "description")
XCTAssertNotEqual(expr1, expr2, "description")
// expr1 > expr2
XCTAssertGreaterThat(expr1, expr2, "description")
// expr1 < expr2
XCTAssertLessThat(expr1, expr2, "description")
// expr1 <= expr2
XCTAssertLessThanOrEqual(expr1, expr2, "description")
// Asserts taht two expressions have the same value within a certain accuracy
XCTAssertEqualWithAccuracy(expr1, expr2, accuracy, "description")
// Asserts that two floating-point values are equal within a specified accuracy.
XCTAssertEqual(49.2827, 49.2826, accuracy: 0.001)
Let’s say you have a model:
struct SignUpFormModel {
let firstName: String
let lastName: String
let email: String
let password: String
let repeatPassword: String
}
Where you would have a method to validate email:
extension SignUpFormModel {
func isValidEmailFormat() -> Bool {
return email.contains("@") && email.contains(".")
}
}
Ok, now it’s time to make a more professional test, but before that to make it more professional let’s think about naming. To name your tests properly is very important to get used to this from the very beginning. Let’s consider this name:
testSignUpFormModel_WhenCreated_EmailShouldHaveValidFormat()
If to break it you can see several parts:
test<Subject><Condition Or State Change><Expected Result>()
The name starts with test — it’s important to start from this word, otherwise, XCTest won’t recognize your methods (you won’t see this triangle icon). Other parts describe your test almost in detail. So, if you will make yourself keep using this naming format then your colleagues sooner or later will say thank you in some time.
Okay, we have an empty test; let’s fill it using a simple pattern called Arrange, Act, Assert.
func testSignUpFormModel_WhenCreated_EmailShouldHaveValidFormat() {
// Arrange (GIVEN)
let firstName = "Maksim"
let lastName = "Kalik"
let email = "[email protected]"
let password = "qwerty123"
let repeatPassword = "qwerty123"
let signUpFormModel = SignUpFormModel(
firstName: firstName,
lastName: lastName,
email: email,
password: password,
repeatPassword: repeatPassword
)
// Act (WHEN)
let isEmailFormatValid = signUpFormModel.isValidEmailFormat()
// Assert (THEN)
XCTAssertTrue(isEmailFormatValid, "Provided valid email address does not have a valid format")
}
Now you can run your first beautiful test and see how it beautifully fit into the list of other tests.
Your first unit test (at least using this described format) will be a great contribution to the project you are working on. If you want to see your project from another point of purpose to make it more robust, then it’s a good time to start writing tests, even if you are working in a startup. Just start from the simple and obvious part of your code, then slowly take other more complicated units to test. Good luck!