I am learning how to write tests for my API requests but having trouble setting up my test's completion code and response model.
I tried using an instance of UserResponse (let userResponse = UserResponse() ) however it required a value for its initializer (from: Decoder) and I have no idea what goes in there. The error I get is:
"Argument type 'Decoder.Protocol' does not conform to expected type 'Decoder'"
Also I am having errors in creating the test's completion handler, I am using the new Swift Result type (Result<UserResponse, Error>). The error I get is:
"Type of expression is ambiguous without more context"
This is an @escaping function but I got an error saying to remove @escaping in the test.
Any ideas on what is wrong? I have marked the trouble code below with comments.
Thank you!
// APP CODE
class SignupViewModel: ObservableObject {
func createAccount(user: UserSignup, completion: @escaping( Result<UserResponse, Error>) -> Void) {
AuthService.createAccount(user: user, completion: completion)
}
}
struct UserSignup: Encodable {
var username: String
var email: String
var password: String
}
struct UserResponse: Decodable {
var user: User
var token: String
}
struct User: Decodable {
var username: String
var email: String
// etc
{ private enum UserKeys }
init(from decoder: Decoder) throws { container / decode code }
}
// TEST CODE
class SignupViewModelTests: XCTestCase {
var sut: SignupViewModel!
override func setUpWithError() throws {
sut = SignupViewModel()
}
override func tearDownWithError() throws {
sut = nil
}
func testCreateAccount_WhenGivenSuccessfulResponse_ReturnsSuccess() {
let userSignup = UserSignup(username: "johnsmith", email: "john@test.com", password: "abc123abc")
// WHAT GOES IN from:??
let userResponse = UserResponse(from: Decoder)
// ERROR: "Type of expression is ambiguous without more context"??
func testCreateAccount_WhenGivenSuccessfulResponse_ReturnsSuccess() {
//arrange
let userSignup = UserSignup(username: "johnsmith", email: "john@test.com", password: "abc123abc")
sut.createAccount(user: UserSignup, completion: ( Result <UserResponse, Error> ) -> Void ) {
XCTAssertEqual(UserResponse.user.username, "johnsmith")
}
}
}
}
To create a UserResponse
in test code, call its synthesized initializer, not the decoder initializer. Something like:
let userResponse = UserResponse(user: User("Chris"), token: "TOKEN")
And to create a closure in test code, you need to give it code. Completion closures in tests have one job, to capture how they were called. Something like:
var capturedResponses: [Result<UserResponse, Error>] = []
sut.createAccount(user: UserSignup, completion: { capturedResponses.append($0) })
This captures the Result
that createAccount(user:completion:)
sends to the completion handler.
…That said, it looks like your view model is directly calling a static function that makes a service call. If the test runs, will it create an actual account somewhere? Or do you have some boundary in place that we can't see?
Instead of directly testing createAccount(user:completion:)
, what you probably want to test is:
If my assumptions are correct, I can show you how to do this.