Skip to content

Refactor unit test cases to use JSON comparison (part 1)#434

Merged
timkimadobe merged 21 commits intoadobe:devfrom
timkimadobe:refactor-for-json-comparison
Jan 8, 2024
Merged

Refactor unit test cases to use JSON comparison (part 1)#434
timkimadobe merged 21 commits intoadobe:devfrom
timkimadobe:refactor-for-json-comparison

Conversation

@timkimadobe
Copy link
Copy Markdown
Contributor

@timkimadobe timkimadobe commented Nov 22, 2023

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 AEPTestUtils
See 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

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • I have signed the Adobe Open Source CLA.
  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

…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
Copy link
Copy Markdown

codecov bot commented Nov 22, 2023

Codecov Report

Merging #434 (f8814bd) into dev (74a67e5) will decrease coverage by 0.29%.
The diff coverage is n/a.

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     

Comment on lines +765 to +769
{
"xdm": {
"implementationDetails": null
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

@timkimadobe timkimadobe Nov 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:
Screenshot 2023-12-14 at 8 02 15 PM

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:

  1. implementationDetails key exists and its value must be nil
  2. implementationDetails key 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 are null
    • In this case, xdm on the actual side has the child element: "identityMap": null meaning that setting "implementationDetails": null on the expected side doesnt cause collection size validation to fail
    • If implementation were to change and "identityMap": null was not present on the actual side, this test case would start failing, even though "identityMap": null is 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

Hopefully my explanation makes sense, please let me know what you think!

If this implicit requirement isn't desirable, some potential alternatives are:

  1. Update the comparison system to add a new mode which allows for validating a key is not present (not equal to nil or implicitly nil by not being present as it is today)
  2. Refactor the validation to use positive validation of what keys are expected and strictly validate the collection size

Comment on lines 817 to 819
XCTAssertNil(payload["xdm.implementationDetails.name"])
XCTAssertNil(payload["xdm.implementationDetails.version"])
XCTAssertNil(payload["xdm.implementationDetails.environment"])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these tests still needed if you are validating the entire JSON request below?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, they're not needed - sorry I forgot to remove these lines! Thanks Kevin

Comment on lines +83 to +102
// 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()
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you double check the indentation here? You may need to convert tabs to spaces if you are using them.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were you referring to the indent being one level higher for the verify section? I think it might be because it's inside the doRequest closure. I checked the whitespace characters and I believe it's using spaces:

Screenshot 2023-11-29 at 4 49 08 PM

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

@timkimadobe timkimadobe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review @kevinlind! Updated based on feedback

Comment on lines +83 to +102
// 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()
})
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were you referring to the indent being one level higher for the verify section? I think it might be because it's inside the doRequest closure. I checked the whitespace characters and I believe it's using spaces:

Screenshot 2023-11-29 at 4 49 08 PM

Comment on lines +765 to +769
{
"xdm": {
"implementationDetails": null
}
}
Copy link
Copy Markdown
Contributor Author

@timkimadobe timkimadobe Nov 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:
Screenshot 2023-12-14 at 8 02 15 PM

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:

  1. implementationDetails key exists and its value must be nil
  2. implementationDetails key 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 are null
    • In this case, xdm on the actual side has the child element: "identityMap": null meaning that setting "implementationDetails": null on the expected side doesnt cause collection size validation to fail
    • If implementation were to change and "identityMap": null was not present on the actual side, this test case would start failing, even though "identityMap": null is 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

Hopefully my explanation makes sense, please let me know what you think!

If this implicit requirement isn't desirable, some potential alternatives are:

  1. Update the comparison system to add a new mode which allows for validating a key is not present (not equal to nil or implicitly nil by not being present as it is today)
  2. Refactor the validation to use positive validation of what keys are expected and strictly validate the collection size

Comment on lines 817 to 819
XCTAssertNil(payload["xdm.implementationDetails.name"])
XCTAssertNil(payload["xdm.implementationDetails.version"])
XCTAssertNil(payload["xdm.implementationDetails.environment"])
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, they're not needed - sorry I forgot to remove these lines! Thanks Kevin

}
"""#

assertEqual(expected: getAnyCodable(expectedJSON)!,
Copy link
Copy Markdown

@cdhoffmann cdhoffmann Nov 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

@timkimadobe timkimadobe Dec 12, 2023

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 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

Copy link
Copy Markdown
Contributor

@kevinlind kevinlind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, though you'll need to fix the build failures before merging.

@timkimadobe timkimadobe merged commit 2c9403f into adobe:dev Jan 8, 2024
@timkimadobe timkimadobe deleted the refactor-for-json-comparison branch January 8, 2024 22:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor functional and integration tests to use new test utils

3 participants