Swift单元测试网络请求遇API违规:多次调用XCTestExpectation fulfill
Hey there, let's break down why you're hitting this API violation - multiple calls made to -[XCTestExpectation fulfill] error and how to fix it.
First, the root cause is straightforward: your XCTestExpectation instance is being called with fulfill() more than once. Each expectation is designed to be satisfied exactly once, so multiple calls trigger this violation. In your network test scenario, this almost always ties back to either:
- Accidental multiple
fulfill()calls in your test closure, or - Your network layer's
requestmethod invoking the completion closure more than once.
Let's walk through concrete fixes and checks based on your code snippet:
1. Audit your test closure for duplicate fulfill() calls
Looking at your partial code, it's easy to accidentally place fulfill() in multiple nested if branches. For example, if you call it inside the json parsing block and in an error handling branch, that's two calls right there.
Bad Example (what you might have):
func testAccessKeys() { let expected = expectation(description: "Run the Access request") sut.request(.Access, data: nil) { finished, response in if response != nil && finished == true { if let json = response as? [String:Any] { // Assert some values expected.fulfill() // First call } } else { // Handle failure expected.fulfill() // Second call } } waitForExpectations(timeout: 5, handler: nil) }
Fixed Version:
Use defer to guarantee fulfill() runs exactly once, no matter which code path the closure takes:
func testAccessKeys() { let expected = expectation(description: "Run the Access request") sut.request(.Access, data: nil) { finished, response in // Ensure fulfill is called once, even if assertions fail defer { expected.fulfill() } // Your assertion logic here guard finished, let response = response as? [String:Any] else { XCTFail("Request failed or returned invalid response") return } XCTAssertNotNil(response["access_key"], "Access key should be present") // Add other relevant assertions } waitForExpectations(timeout: 5) { error in if let error = error { XCTFail("Timed out waiting for access request: \(error.localizedDescription)") } } }
2. Check your network layer's request implementation
If the test code looks clean, the issue might be in your sut (system under test) itself. Common culprits include:
- The
requestmethod calls the completion closure on both success and error paths without proper guard clauses - Duplicate callback registrations (e.g., adding a listener multiple times for the same request)
- Race conditions where network events trigger the closure more than once
Add print statements or breakpoints inside your request method to count how many times the completion closure is invoked. If it's more than once, you'll need to fix that logic to ensure the closure is called exactly once per request.
3. Enable strict over-fulfillment checking (optional)
To get clearer debug info, enable assertForOverFulfill on your expectation. This will throw a more specific error pointing to where the duplicate fulfill() is happening:
let expected = expectation(description: "Run the Access request") expected.assertForOverFulfill = true // Enforce single fulfillment
Content的提问来源于stack exchange,提问作者user7684436

