Testing CRUD Operations with Setup and Teardown

Welcome to another step in our journey to mastering automated API testing with Swift. So far, you've learned how to organize tests using XCTest classes and setup methods. Today, we will focus on automating tests for CRUD operations — Create, Read, Update, and Delete — which are integral actions for managing data in any application that uses RESTful APIs. Automated testing of these operations is essential to ensure that APIs function correctly and modify resources as expected. Thorough testing of CRUD operations will help you catch issues early and ensure API reliability.

Implementing Setup and Teardown for CRUD Operations with XCTest

To effectively manage setup and teardown for CRUD operations, we utilize the setUp() and tearDown() methods in XCTest. These methods automate the process, ensuring each test starts with the right conditions and ends without leaving any trace. This means every test begins and ends with a clean state, which is crucial to avoid any interference between tests.

Here's an example of how the setup and teardown are structured in Swift:

Swift
1import XCTest 2 3class TestCRUDOperations: XCTestCase { 4 var todoId: Int? 5 let baseUrl = "http://localhost:8000" 6 7 override func setUp() { 8 super.setUp() 9 // Setup Actions: Prepare a new todo 10 let todoData: [String: Any] = [ 11 "title": "Setup Todo", 12 "description": "For testing CRUD operations", 13 "done": false 14 ] 15 let expectation = self.expectation(description: "Create Todo") 16 17 var request = URLRequest(url: URL(string: "\(baseUrl)/todos")!) 18 request.httpMethod = "POST" 19 request.httpBody = try? JSONSerialization.data(withJSONObject: todoData) 20 request.setValue("application/json", forHTTPHeaderField: "Content-Type") 21 22 URLSession.shared.dataTask(with: request) { data, response, error in 23 if let data = data, let response = response as? HTTPURLResponse, response.statusCode == 201 { 24 let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] 25 self.todoId = json?["id"] as? Int 26 } 27 expectation.fulfill() 28 }.resume() 29 30 waitForExpectations(timeout: 5, handler: nil) 31 } 32 33 override func tearDown() { 34 // Teardown Actions: Clean up by deleting the todo 35 if let todoId = todoId { 36 let expectation = self.expectation(description: "Delete Todo") 37 38 var request = URLRequest(url: URL(string: "\(baseUrl)/todos/\(todoId)")!) 39 request.httpMethod = "DELETE" 40 41 URLSession.shared.dataTask(with: request) { _, _, _ in 42 expectation.fulfill() 43 }.resume() 44 45 waitForExpectations(timeout: 5, handler: nil) 46 } 47 super.tearDown() 48 } 49}

Within these methods, the setup actions occur before each test to create a todo item, ensuring the test environment has the necessary data. The teardown actions delete the created todo, maintaining a clean slate for subsequent tests. This ensures consistent, independent test runs free from interference caused by leftover data.

Defining CRUD Tests within the Class

With setup and teardown processes in place using XCTest, we establish a consistent environment for our tests. Now, we'll define the specific tests for each CRUD operation within the TestCRUDOperations class. These tests will leverage the setup todo item, ensuring that we assess the ability to create, read, update, and delete resources accurately. Let's explore each operation through dedicated test functions.

Testing Read Operation

In our CRUD operations, the Read test checks if we can successfully retrieve a todo item. Using URLSession, we fetch the todo item created during the setup. The test verifies the HTTP status code is 200, indicating success, and it asserts that the data returned matches our expectations. This confirms that our API's read functionality works correctly.

Swift
1func testReadTodo() { 2 let expectation = self.expectation(description: "Read Todo") 3 4 guard let todoId = todoId else { 5 XCTFail("Todo ID is nil") 6 return 7 } 8 9 let url = URL(string: "\(baseUrl)/todos/\(todoId)")! 10 URLSession.shared.dataTask(with: url) { data, response, error in 11 if let data = data, let response = response as? HTTPURLResponse, response.statusCode == 200 { 12 let fetchedTodo = try? JSONSerialization.jsonObject(with: data) as? [String: Any] 13 XCTAssertEqual(fetchedTodo?["title"] as? String, "Setup Todo") 14 XCTAssertEqual(fetchedTodo?["description"] as? String, "For testing CRUD operations") 15 XCTAssertEqual(fetchedTodo?["done"] as? Bool, false) 16 } else { 17 XCTFail("Failed to fetch todo") 18 } 19 expectation.fulfill() 20 }.resume() 21 22 waitForExpectations(timeout: 5, handler: nil) 23}
Testing Update Operation with PATCH

The Update operation using PATCH focuses on modifying specific fields of a todo item. Here, we send a PATCH request to change the done status to True. The test checks if the status code is 200, confirming the update was successful. We also verify the field was accurately updated in the API. This ensures that partial updates with PATCH are functioning as intended.

Swift
1func testUpdateTodoWithPatch() { 2 let expectation = self.expectation(description: "Update Todo with PATCH") 3 4 guard let todoId = todoId else { 5 XCTFail("Todo ID is nil") 6 return 7 } 8 9 let updateData: [String: Any] = ["done": true] 10 var request = URLRequest(url: URL(string: "\(baseUrl)/todos/\(todoId)")!) 11 request.httpMethod = "PATCH" 12 request.httpBody = try? JSONSerialization.data(withJSONObject: updateData) 13 request.setValue("application/json", forHTTPHeaderField: "Content-Type") 14 15 URLSession.shared.dataTask(with: request) { data, response, error in 16 if let data = data, let response = response as? HTTPURLResponse, response.statusCode == 200 { 17 let updatedTodo = try? JSONSerialization.jsonObject(with: data) as? [String: Any] 18 XCTAssertEqual(updatedTodo?["done"] as? Bool, true) 19 } else { 20 XCTFail("Failed to update todo") 21 } 22 expectation.fulfill() 23 }.resume() 24 25 waitForExpectations(timeout: 5, handler: nil) 26}
Testing Update Operation with PUT

For a complete replacement of fields, the Update operation using PUT is employed. This test sends a PUT request to replace all fields of the todo item with new data. We assert the status code is 200, indicating the operation succeeded, and confirm that all fields match the updated values. This test validates that full updates with PUT are correctly processed by the API.

Swift
1func testUpdateTodoWithPut() { 2 let expectation = self.expectation(description: "Update Todo with PUT") 3 4 guard let todoId = todoId else { 5 XCTFail("Todo ID is nil") 6 return 7 } 8 9 let putData: [String: Any] = [ 10 "title": "Updated Title", 11 "description": "Updated Description", 12 "done": true 13 ] 14 var request = URLRequest(url: URL(string: "\(baseUrl)/todos/\(todoId)")!) 15 request.httpMethod = "PUT" 16 request.httpBody = try? JSONSerialization.data(withJSONObject: putData) 17 request.setValue("application/json", forHTTPHeaderField: "Content-Type") 18 19 URLSession.shared.dataTask(with: request) { data, response, error in 20 if let data = data, let response = response as? HTTPURLResponse, response.statusCode == 200 { 21 let updatedTodo = try? JSONSerialization.jsonObject(with: data) as? [String: Any] 22 XCTAssertEqual(updatedTodo?["title"] as? String, "Updated Title") 23 XCTAssertEqual(updatedTodo?["description"] as? String, "Updated Description") 24 XCTAssertEqual(updatedTodo?["done"] as? Bool, true) 25 } else { 26 XCTFail("Failed to update todo") 27 } 28 expectation.fulfill() 29 }.resume() 30 31 waitForExpectations(timeout: 5, handler: nil) 32}
Testing Delete Operation

The Delete test checks if a todo item can be removed successfully. A DELETE request is sent to the API, and the test verifies the status code is 204, signifying a successful deletion with no content returned. To confirm the deletion, we attempt to retrieve the same todo item and expect a 404 status code, indicating it no longer exists. This ensures the API's delete functionality behaves as expected.

Swift
1func testDeleteTodo() { 2 let expectation = self.expectation(description: "Delete Todo") 3 4 guard let todoId = todoId else { 5 XCTFail("Todo ID is nil") 6 return 7 } 8 9 var request = URLRequest(url: URL(string: "\(baseUrl)/todos/\(todoId)")!) 10 request.httpMethod = "DELETE" 11 12 URLSession.shared.dataTask(with: request) { _, response, _ in 13 if let response = response as? HTTPURLResponse, response.statusCode == 204 { 14 let getDeletedExpectation = self.expectation(description: "Get Deleted Todo") 15 16 let url = URL(string: "\(baseUrl)/todos/\(todoId)")! 17 URLSession.shared.dataTask(with: url) { _, response, _ in 18 if let response = response as? HTTPURLResponse { 19 XCTAssertEqual(response.statusCode, 404) 20 } else { 21 XCTFail("Failed to confirm deletion") 22 } 23 getDeletedExpectation.fulfill() 24 }.resume() 25 26 self.waitForExpectations(timeout: 5, handler: nil) 27 } else { 28 XCTFail("Failed to delete todo") 29 } 30 expectation.fulfill() 31 }.resume() 32 33 waitForExpectations(timeout: 5, handler: nil) 34}
Summary and Practice Preparation

In today's lesson, we've delved into the essentials of testing CRUD operations with setup and teardown using XCTest. You've seen how automating these processes helps maintain a structured and reliable testing environment. Now, it's your turn to practice these concepts with hands-on exercises that will reinforce your understanding and confidence in applying these techniques. As you continue to build your skills, you'll be better equipped to ensure API robustness and reliability. Keep up the great work, and prepare to explore testing authenticated endpoints moving forward!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal