-
Notifications
You must be signed in to change notification settings - Fork 24
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Testable NetworkRequest #404
Testable NetworkRequest #404
Conversation
… NetworkRequest Override Equatable and Hashable implementations for use in testing comparisons
…orkRequest as keys Refactor usages to use TestableNetworkRequest methods Rename setResponseFor to addResponseFor to more accurately reflect what the method does
By doing the countdown AFTER notifying the completion handler, awaits on the expected network request can properly gate the rest of the test case logic (for example, if the test case resets the mock network service, there isn't a race condition between the reset and the get mock response, due to prematurely ungated await)
Codecov Report
@@ Coverage Diff @@
## dev #404 +/- ##
=======================================
Coverage 96.77% 96.77%
=======================================
Files 27 27
Lines 1671 1671
=======================================
Hits 1617 1617
Misses 54 54 |
// will need to be updated accordingly to handle that case. | ||
|
||
// MARK: - Equatable (ObjC) conformance | ||
override func isEqual(_ object: Any?) -> Bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add comment stating isEqual
only checks URL scheme, host, path and http method, but not the query parameters for equality.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added comment!
} | ||
|
||
// MARK: - Hashable (ObjC) conformance | ||
public override var hash: Int { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add comment stating hash
only includes URL scheme, host, path and http method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added comment!
func addResponseFor(networkRequest: NetworkRequest, responseConnection: HttpConnection) { | ||
let testableNetworkRequest = TestableNetworkRequest(from: networkRequest) | ||
if networkResponses[testableNetworkRequest] != nil { | ||
networkResponses[testableNetworkRequest]?.append(responseConnection) | ||
} | ||
else { | ||
networkResponses[testableNetworkRequest] = [responseConnection] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should still be setResponseFor
and the networkResponses
type should be [NetworkRequest: HttpConnection]
unless you see a need for an array of HttpConnection
s for a single request. If you see here in MockNetworkRequest, only the first HttpConnection is used which I believe is a side effect of not being able to group similar NetworkRequest objects together. Now with this TestableNetworkRequest, we can just store a single HttpConnection for the set of "equal" NetworkRequests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a great point! Looking at the default NetworkService connectAsync implementation, I think there can only ever be a single HttpConnection
response for a given request, based on the dataTask
.
The only case I can think of multiple connections for the same request is maybe a recoverable failure response with retry? But even then I think we can use staggered/interleaved expectations instead of capturing both with the same key
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made the following changes:
- Reverted the method name change from
addResponseFor
tosetResponseFor
- Updated param
responseConnection
to beHttpConnection?
- This has the subtle effect of removing any existing dictionary entry for the given
NetworkRequest
if the HttpConnection value passed isnil
- This has the subtle effect of removing any existing dictionary entry for the given
- Updated get__ResponseFor methods to be singular instead of plural
- Updated integration test cases to check for single response (non-nil instead of count), and removed array access logic
@@ -58,7 +56,7 @@ class MockNetworkService: Networking { | |||
/// Sets the mock `HttpConnection` response connection for a given `NetworkRequest`. Should only be used | |||
/// when in mock mode. | |||
func setMockResponseFor(networkRequest: NetworkRequest, responseConnection: HttpConnection?) { | |||
helper.setResponseFor(networkRequest: networkRequest, responseConnection: responseConnection) | |||
helper.addResponseFor(networkRequest: networkRequest, responseConnection: responseConnection ?? defaultMockResponse(networkRequest.url)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding a default isn't strictly needed as because of the else
clause in the if let response ...
statement above on line 37.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the defaults here, and updated the MockNetworkService connectAsync to own the default directly
.filter { networkRequest.isCustomEqual($0.key) } | ||
.map { $0.value } | ||
/// Gets all network responses for the given `NetworkRequest` | ||
func getResponsesFor(networkRequest: NetworkRequest) -> [HttpConnection]? { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After changing networkResponses
to [NetworkRequest: HttpConnection]
, this can return just HttpConnection?
instead of an array.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated return type to HttpConnection?
Refactor related logic for getting and setting, and usage sites
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks so much for the review @kevinlind! Updated based on feedback
func addResponseFor(networkRequest: NetworkRequest, responseConnection: HttpConnection) { | ||
let testableNetworkRequest = TestableNetworkRequest(from: networkRequest) | ||
if networkResponses[testableNetworkRequest] != nil { | ||
networkResponses[testableNetworkRequest]?.append(responseConnection) | ||
} | ||
else { | ||
networkResponses[testableNetworkRequest] = [responseConnection] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a great point! Looking at the default NetworkService connectAsync implementation, I think there can only ever be a single HttpConnection
response for a given request, based on the dataTask
.
The only case I can think of multiple connections for the same request is maybe a recoverable failure response with retry? But even then I think we can use staggered/interleaved expectations instead of capturing both with the same key
.filter { networkRequest.isCustomEqual($0.key) } | ||
.map { $0.value } | ||
/// Gets all network responses for the given `NetworkRequest` | ||
func getResponsesFor(networkRequest: NetworkRequest) -> [HttpConnection]? { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated return type to HttpConnection?
// will need to be updated accordingly to handle that case. | ||
|
||
// MARK: - Equatable (ObjC) conformance | ||
override func isEqual(_ object: Any?) -> Bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added comment!
} | ||
|
||
// MARK: - Hashable (ObjC) conformance | ||
public override var hash: Int { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added comment!
func addResponseFor(networkRequest: NetworkRequest, responseConnection: HttpConnection) { | ||
let testableNetworkRequest = TestableNetworkRequest(from: networkRequest) | ||
if networkResponses[testableNetworkRequest] != nil { | ||
networkResponses[testableNetworkRequest]?.append(responseConnection) | ||
} | ||
else { | ||
networkResponses[testableNetworkRequest] = [responseConnection] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made the following changes:
- Reverted the method name change from
addResponseFor
tosetResponseFor
- Updated param
responseConnection
to beHttpConnection?
- This has the subtle effect of removing any existing dictionary entry for the given
NetworkRequest
if the HttpConnection value passed isnil
- This has the subtle effect of removing any existing dictionary entry for the given
- Updated get__ResponseFor methods to be singular instead of plural
- Updated integration test cases to check for single response (non-nil instead of count), and removed array access logic
@@ -58,7 +56,7 @@ class MockNetworkService: Networking { | |||
/// Sets the mock `HttpConnection` response connection for a given `NetworkRequest`. Should only be used | |||
/// when in mock mode. | |||
func setMockResponseFor(networkRequest: NetworkRequest, responseConnection: HttpConnection?) { | |||
helper.setResponseFor(networkRequest: networkRequest, responseConnection: responseConnection) | |||
helper.addResponseFor(networkRequest: networkRequest, responseConnection: responseConnection ?? defaultMockResponse(networkRequest.url)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the defaults here, and updated the MockNetworkService connectAsync to own the default directly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One minor comment about documentation, otherwise looks good.
return networkResponses | ||
.filter { networkRequest.isCustomEqual($0.key) } | ||
.map { $0.value } | ||
/// Gets all network responses for the given `NetworkRequest` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Update comment "Get network response for the given NetworkRequest", plus add comment for parameter and return value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated method docs!
Description
This PR:
TestableNetworkRequest
that uses the@testable import AEPServices
to gainopen
inheritable access to theNetworkRequest
class.TestableNetworkRequest
overrides the Equatable and Hashable conformance ofNetworkRequest
for direct use as a dictionary key using custom logic.Equatable
andHashable
must have exactly the same results logically for what they consider the "same" elementdefaultMockResponse
inMockNetworkService
that creates a default response provided a valid URLNetworkServiceHelper
data structs and methods to account for the new key equality behavior, and refactors usages accordinglyMockNetworkService
connectAsync
implementation by moving the countdown to after the callback is notified, to properly gate test case logic using awaitRelated Issue
Motivation and Context
How Has This Been Tested?
Screenshots (if appropriate):
Types of changes
Checklist: