Refactor unit test cases to use JSON comparison (part 1)#434
Refactor unit test cases to use JSON comparison (part 1)#434timkimadobe merged 21 commits intoadobe:devfrom
Conversation
…dsNetworkRequest_returnsTrue
testProcessHit_experienceEvent_withDatastreamOverrideSet_sendsNetworkRequest_returnsTrue change intent:
1. Refactor existing mock network service method usage to AEPTestUtils MockNetworkService, which explicitly requires URL
a. Has the downstream effect of more strictly validating the request
2. Take advantage of new functionality in mockNetworkService.assertAllNetworkRequestExpectations to validate both:
a. Expected events
b. No unexpected events (toggleable)
3. Remove mock response from test case setup since it is the same as the mock response set in helper assertProcessHit
…urnsTrue Remove mock response from test case setup since it is the same as the mock response set in helper assertProcessHit
To take advantage of new functionality in mockNetworkService.assertAllNetworkRequestExpectations to validate:
a. Expected events
b. No unexpected events (toggleable)
c. No requests sent out (no expectations + no unexpected events allowed)
Codecov Report
Additional details and impacted files@@ Coverage Diff @@
## dev #434 +/- ##
==========================================
- Coverage 96.76% 96.47% -0.29%
==========================================
Files 27 27
Lines 1730 1730
==========================================
- Hits 1674 1669 -5
- Misses 56 61 +5 |
| { | ||
| "xdm": { | ||
| "implementationDetails": null | ||
| } | ||
| } |
There was a problem hiding this comment.
Does this mean that "implementationDetails" is not added to the "xdm" object, or that the field "implementationDetails" is added but has a value of "null"? I would expect "implementationDetails" to not be included in the request.
There was a problem hiding this comment.
UPDATE: Validating a key is not present is now possible with the changes in: adobe/aepsdk-testutils-ios#15
See example mocked test failure when keys are present:

Outdated:
The way the validation works currently by default, putting: "implementationDetails": null in the expected JSON means that either case on the actual side causes validation to pass:
implementationDetailskey exists and its value must benilimplementationDetailskey does not exist, however an implicit requirement of the comparison system is that the parent collection has at least the same number of children elements as fields you're validating arenull- In this case,
xdmon the actual side has the child element:"identityMap": nullmeaning that setting"implementationDetails": nullon the expected side doesnt cause collection size validation to fail - If implementation were to change and
"identityMap": nullwas not present on the actual side, this test case would start failing, even though"identityMap": nullis not being directly tested - That is, the comparison system describes positive validation well (the presence of keys and their values) but negative validation (the absence of keys) is a bit roundabout
- In this case,
Hopefully my explanation makes sense, please let me know what you think!
If this implicit requirement isn't desirable, some potential alternatives are:
- Update the comparison system to add a new mode which allows for validating a key is not present (not equal to
nilor implicitlynilby not being present as it is today)- Building on the changes in: Refactor JSON comparison options handling aepsdk-testutils-ios#9
- Refactor the validation to use positive validation of what keys are expected and strictly validate the collection size
| XCTAssertNil(payload["xdm.implementationDetails.name"]) | ||
| XCTAssertNil(payload["xdm.implementationDetails.version"]) | ||
| XCTAssertNil(payload["xdm.implementationDetails.environment"]) |
There was a problem hiding this comment.
Are these tests still needed if you are validating the entire JSON request below?
There was a problem hiding this comment.
No, they're not needed - sorry I forgot to remove these lines! Thanks Kevin
| // verify | ||
| let networkRequests = self.mockNetworkService.getNetworkRequestsWith(url: self.url, httpMethod: .post) | ||
| guard let networkRequest = networkRequests.first else { | ||
| XCTFail("Unable to find valid network request.") | ||
| return | ||
| } | ||
|
|
||
| XCTAssertTrue(success) | ||
| XCTAssertNil(retryInterval) | ||
| XCTAssertEqual(1, networkRequests.count) | ||
| XCTAssertEqual(testHeaders.count + self.defaultNetworkingHeaders.count, networkRequest.httpHeaders.count) | ||
| for header in testHeaders { | ||
| XCTAssertNotNil(networkRequest.httpHeaders[header.key]) | ||
| } | ||
|
|
||
| for header in self.defaultNetworkingHeaders { | ||
| XCTAssertNotNil(networkRequest.httpHeaders[header]) | ||
| } | ||
| expectation.fulfill() | ||
| }) |
There was a problem hiding this comment.
Can you double check the indentation here? You may need to convert tabs to spaces if you are using them.
There was a problem hiding this comment.
The previous code was indented from the "completion" block but now it appears to be indented from the "networkService.doRequest" block. If you look at the next few test cases which call doRequest you can see the completion closure code is indented under the "completion" parameter name. As long as you've run SwiftLint to autocorrect the formatting, then I think this is fine. I was just confused as to why the indentation changed.
timkimadobe
left a comment
There was a problem hiding this comment.
Thanks for the review @kevinlind! Updated based on feedback
| // verify | ||
| let networkRequests = self.mockNetworkService.getNetworkRequestsWith(url: self.url, httpMethod: .post) | ||
| guard let networkRequest = networkRequests.first else { | ||
| XCTFail("Unable to find valid network request.") | ||
| return | ||
| } | ||
|
|
||
| XCTAssertTrue(success) | ||
| XCTAssertNil(retryInterval) | ||
| XCTAssertEqual(1, networkRequests.count) | ||
| XCTAssertEqual(testHeaders.count + self.defaultNetworkingHeaders.count, networkRequest.httpHeaders.count) | ||
| for header in testHeaders { | ||
| XCTAssertNotNil(networkRequest.httpHeaders[header.key]) | ||
| } | ||
|
|
||
| for header in self.defaultNetworkingHeaders { | ||
| XCTAssertNotNil(networkRequest.httpHeaders[header]) | ||
| } | ||
| expectation.fulfill() | ||
| }) |
| { | ||
| "xdm": { | ||
| "implementationDetails": null | ||
| } | ||
| } |
There was a problem hiding this comment.
UPDATE: Validating a key is not present is now possible with the changes in: adobe/aepsdk-testutils-ios#15
See example mocked test failure when keys are present:

Outdated:
The way the validation works currently by default, putting: "implementationDetails": null in the expected JSON means that either case on the actual side causes validation to pass:
implementationDetailskey exists and its value must benilimplementationDetailskey does not exist, however an implicit requirement of the comparison system is that the parent collection has at least the same number of children elements as fields you're validating arenull- In this case,
xdmon the actual side has the child element:"identityMap": nullmeaning that setting"implementationDetails": nullon the expected side doesnt cause collection size validation to fail - If implementation were to change and
"identityMap": nullwas not present on the actual side, this test case would start failing, even though"identityMap": nullis not being directly tested - That is, the comparison system describes positive validation well (the presence of keys and their values) but negative validation (the absence of keys) is a bit roundabout
- In this case,
Hopefully my explanation makes sense, please let me know what you think!
If this implicit requirement isn't desirable, some potential alternatives are:
- Update the comparison system to add a new mode which allows for validating a key is not present (not equal to
nilor implicitlynilby not being present as it is today)- Building on the changes in: Refactor JSON comparison options handling aepsdk-testutils-ios#9
- Refactor the validation to use positive validation of what keys are expected and strictly validate the collection size
| XCTAssertNil(payload["xdm.implementationDetails.name"]) | ||
| XCTAssertNil(payload["xdm.implementationDetails.version"]) | ||
| XCTAssertNil(payload["xdm.implementationDetails.environment"]) |
There was a problem hiding this comment.
No, they're not needed - sorry I forgot to remove these lines! Thanks Kevin
| } | ||
| """# | ||
|
|
||
| assertEqual(expected: getAnyCodable(expectedJSON)!, |
There was a problem hiding this comment.
This is more of an idea around the assertEqual method. What do you think about overloading the method to take the raw json instead, and internally doing the anycodable conversions? Will make the API easier to use in our tests. Not sure if it should just replace this method, or if it should be overloaded in case we want to support both.
There was a problem hiding this comment.
I think this is a great idea! I feel like there is quite a bit of boilerplate we can remove from the method callsite. I've implemented a new protocol that handles type conversion to AnyCodable here:
adobe/aepsdk-testutils-ios#16
kevinlind
left a comment
There was a problem hiding this comment.
Looks good, though you'll need to fix the build failures before merging.

Note
This is the first part in a multi-part series of PRs that will refactor existing Edge extension unit and functional tests to use the JSON comparison tool from
AEPTestUtilsSee part 2: #436
Description
This PR refactors some of the Edge extension unit tests to use JSON comparison
Related Issue
Motivation and Context
How Has This Been Tested?
Screenshots (if appropriate):
Types of changes
Checklist: