Skip to content

Commit bc47f47

Browse files
committed
test(network-details): Add integration tests for completion handler swizzling
Verify that the swizzled dataTaskWithRequest:completionHandler: and dataTaskWithURL:completionHandler: actually fire and populate network details on breadcrumbs using real HTTP requests to postman-echo.com.
1 parent 587627c commit bc47f47

1 file changed

Lines changed: 148 additions & 0 deletions

File tree

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#if os(iOS) || os(tvOS)
2+
3+
@_spi(Private) @testable import Sentry
4+
@_spi(Private) import SentryTestUtils
5+
import XCTest
6+
7+
/// Integration tests that verify the completion-handler swizzling for replay's
8+
/// network detail capture actually works end-to-end.
9+
///
10+
/// Unlike the unit tests in SentryNetworkTrackerTests (which call tracker
11+
/// methods directly), these tests start the SDK, make real HTTP requests,
12+
/// and assert that the swizzled completion handler fires and populates
13+
/// network details on the resulting breadcrumb.
14+
///
15+
/// Uses postman-echo.com so no local test server is required.
16+
class SentryNetworkDetailSwizzlingTests: XCTestCase {
17+
18+
private let echoURL = URL(string: "https://postman-echo.com/get")!
19+
20+
override func setUp() {
21+
super.setUp()
22+
23+
let options = Options()
24+
options.dsn = TestConstants.dsnAsString(username: "SentryNetworkDetailSwizzlingTests")
25+
options.tracesSampleRate = 1.0
26+
options.enableNetworkBreadcrumbs = true
27+
options.sessionReplay.networkDetailAllowUrls = ["postman-echo.com"]
28+
options.sessionReplay.networkCaptureBodies = true
29+
SentrySDK.start(options: options)
30+
}
31+
32+
override func tearDown() {
33+
super.tearDown()
34+
clearTestState()
35+
}
36+
37+
// MARK: - Tests
38+
39+
/// Verifies the swizzle of `-[NSURLSession dataTaskWithRequest:completionHandler:]`
40+
/// captures response details into the breadcrumb.
41+
func testDataTaskWithRequest_completionHandler_capturesNetworkDetails() throws {
42+
let transaction = SentrySDK.startTransaction(
43+
name: "Test", operation: "test", bindToScope: true
44+
)
45+
46+
let expect = expectation(description: "Request completed")
47+
expect.assertForOverFulfill = false
48+
49+
let session = URLSession(configuration: .default)
50+
let request = URLRequest(url: echoURL)
51+
52+
var receivedData: Data?
53+
var receivedResponse: URLResponse?
54+
var receivedError: Error?
55+
56+
let task = session.dataTask(with: request) { data, response, error in
57+
receivedData = data
58+
receivedResponse = response
59+
receivedError = error
60+
expect.fulfill()
61+
}
62+
defer { task.cancel() }
63+
64+
task.resume()
65+
wait(for: [expect], timeout: 5)
66+
67+
transaction.finish()
68+
69+
// Original completion handler received valid data
70+
XCTAssertNil(receivedError, "Request should succeed")
71+
XCTAssertNotNil(receivedData, "Should receive response data")
72+
let httpResponse = try XCTUnwrap(receivedResponse as? HTTPURLResponse)
73+
XCTAssertEqual(httpResponse.statusCode, 200)
74+
75+
// Network details were captured via the swizzled completion handler
76+
let breadcrumb = try lastHTTPBreadcrumb(for: echoURL)
77+
let details = try XCTUnwrap(
78+
breadcrumb.data?[SentryReplayNetworkDetails.replayNetworkDetailsKey] as? SentryReplayNetworkDetails,
79+
"Swizzled completion handler should have populated network details on the breadcrumb"
80+
)
81+
let serialized = details.serialize()
82+
XCTAssertEqual(serialized["statusCode"] as? Int, 200)
83+
XCTAssertNotNil(serialized["response"], "Response details should be captured")
84+
}
85+
86+
/// Verifies the swizzle of `-[NSURLSession dataTaskWithURL:completionHandler:]`
87+
/// captures response details into the breadcrumb.
88+
func testDataTaskWithURL_completionHandler_capturesNetworkDetails() throws {
89+
let transaction = SentrySDK.startTransaction(
90+
name: "Test", operation: "test", bindToScope: true
91+
)
92+
93+
let expect = expectation(description: "Request completed")
94+
expect.assertForOverFulfill = false
95+
96+
let session = URLSession(configuration: .default)
97+
98+
var receivedData: Data?
99+
var receivedResponse: URLResponse?
100+
var receivedError: Error?
101+
102+
let task = session.dataTask(with: echoURL) { data, response, error in
103+
receivedData = data
104+
receivedResponse = response
105+
receivedError = error
106+
expect.fulfill()
107+
}
108+
defer { task.cancel() }
109+
110+
task.resume()
111+
wait(for: [expect], timeout: 5)
112+
113+
transaction.finish()
114+
115+
// Original completion handler received valid data
116+
XCTAssertNil(receivedError, "Request should succeed")
117+
XCTAssertNotNil(receivedData, "Should receive response data")
118+
let httpResponse = try XCTUnwrap(receivedResponse as? HTTPURLResponse)
119+
XCTAssertEqual(httpResponse.statusCode, 200)
120+
121+
// Network details were captured via the swizzled completion handler
122+
let breadcrumb = try lastHTTPBreadcrumb(for: echoURL)
123+
let details = try XCTUnwrap(
124+
breadcrumb.data?[SentryReplayNetworkDetails.replayNetworkDetailsKey] as? SentryReplayNetworkDetails,
125+
"Swizzled completion handler should have populated network details on the breadcrumb"
126+
)
127+
let serialized = details.serialize()
128+
XCTAssertEqual(serialized["statusCode"] as? Int, 200)
129+
XCTAssertNotNil(serialized["response"], "Response details should be captured")
130+
}
131+
132+
// MARK: - Helpers
133+
134+
/// Finds the most recent HTTP breadcrumb whose URL matches the given URL.
135+
private func lastHTTPBreadcrumb(for url: URL) throws -> Breadcrumb {
136+
let scope = SentrySDKInternal.currentHub().scope
137+
let breadcrumbs = try XCTUnwrap(
138+
Dynamic(scope).breadcrumbArray as [Breadcrumb]?,
139+
"Scope should contain breadcrumbs"
140+
)
141+
let matching = breadcrumbs.filter {
142+
$0.category == "http" && ($0.data?["url"] as? String)?.contains(url.host ?? "") == true
143+
}
144+
return try XCTUnwrap(matching.last, "Should find an HTTP breadcrumb for \(url)")
145+
}
146+
}
147+
148+
#endif // os(iOS) || os(tvOS)

0 commit comments

Comments
 (0)