Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up RemoteConfigFeature+Helpers and RemoteConfigFeatureFlagToolsViewModel #1924

Merged
merged 5 commits into from
Feb 1, 2024

Conversation

amy-at-kickstarter
Copy link
Contributor

@amy-at-kickstarter amy-at-kickstarter commented Jan 31, 2024

📲 What

Remove boilerplate from our feature flag code.

🤔 Why

  1. I was about to add a new feature, and the amount of copy-paste was bothering me.
  2. This particular copy-pasted code seemed really easy to make mistakes in. We have tests for it, but those tests were also easy to put mistakes in!

👀 See

Note the test runs for the three commits below - I did the code cleanup first, waited for the tests to pass, and then did the test cleanup. Just to ensure I didn't break both the code and tests simultaneously.


for feature in RemoteConfigFeature.allCases {
mockRemoteConfigClient.features[feature.rawValue] = false
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

You'll still need to re-record the snapshot when you add a new feature, but won't need to modify this code.

@@ -88,11 +88,11 @@ internal class TestCase: XCTestCase {
}

internal func preferredSimulatorCheck() {
let deviceName = ProcessInfo().environment["SIMULATOR_DEVICE_NAME"]
let deviceName = ProcessInfo().environment["SIMULATOR_VERSION_INFO"]
Copy link
Contributor Author

@amy-at-kickstarter amy-at-kickstarter Jan 31, 2024

Choose a reason for hiding this comment

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

@ifosli This was a funny catch. I always name my snapshot simulators something (this one is "New New Snapshot Simulator"). It looks like SIMULATOR_DEVICE_NAME would return "New New Snapshot Simulator" whereas SIMULATOR_VERSION_INFO is like "CoreSimulator 494.33 - Device: iPhone 8 Plus - Runtime: iOS 11.2 (15C107) - DeviceType: iPhone 8 Plus".

Less elegant than your original == but I reaaaaally like my named simulators!

Copy link
Contributor

Choose a reason for hiding this comment

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

Makes sense! I didn't even know you could re-name simulators, so thanks for handling that case!

@@ -3,111 +3,102 @@ import Prelude
import XCTest

final class RemoteConfigFeatureHelpersTests: TestCase {
func testBlockUsers_RemoteConfig_FeatureFlag_False() {
func assertFeatureIsFalse(_ featureFlag: Bool,
whenRemoteConfigFeatureIsFalse feature: RemoteConfigFeature) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Normally I'm fine with boilerplate in tests, but in this case, I think the boilerplate was impeding clarity.

@amy-at-kickstarter amy-at-kickstarter self-assigned this Jan 31, 2024
@amy-at-kickstarter amy-at-kickstarter marked this pull request as ready for review January 31, 2024 21:48
Copy link
Contributor

@ifosli ifosli 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 making this nicer! It looks good apart from one of the tests that I think is currently not actually testing anything.


withEnvironment(remoteConfigClient: mockRemoteConfigClient) {
XCTAssertTrue(featureBlockUsersEnabled())
XCTAssertFalse(featureFlag)
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think these tests are working - we should pass in the featureFlag as a function instead so that it can be evaluated after the remoteConfig has been updated. In particular, I think this test should XCTAssertTrue(featureFlag) instead, and the fact that it passes the way it's written is concerning.

Also, if we want less boilerplate, thoughts on combining these into one function? Ie a testFeatureMatchesRemoteConfig and have it test both the true and the false case? I'm okay with them staying separate, though!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hahaha, OK, excellent catch here - on both bugs 😆 I see what you mean. Let me take another stab at this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As for combining the tests into one method - I just tried it out but honestly, all the closures make it look kind of ugly 😅 So just leaving them in separate test methods for now.

@@ -88,11 +88,11 @@ internal class TestCase: XCTestCase {
}

internal func preferredSimulatorCheck() {
let deviceName = ProcessInfo().environment["SIMULATOR_DEVICE_NAME"]
let deviceName = ProcessInfo().environment["SIMULATOR_VERSION_INFO"]
Copy link
Contributor

Choose a reason for hiding this comment

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

Makes sense! I didn't even know you could re-name simulators, so thanks for handling that case!


withEnvironment(remoteConfigClient: mockRemoteConfigClient) {
XCTAssertTrue(featureBlockUsersEnabled())
XCTAssertTrue(checkFeatureFlag())
Copy link
Contributor Author

Choose a reason for hiding this comment

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

😅 Great catch @ifosli. Soon as I swapped this to XCTAssertTrue the tests started failing!

Copy link
Contributor

Choose a reason for hiding this comment

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

Looks good now!

self.assertFeatureIsFalse(featureBlockUsersEnabled(), whenRemoteConfigFeatureIsFalse: .blockUsersEnabled)
self.assertFeatureIsFalse(whenRemoteConfigFeatureIsFalse: .blockUsersEnabled) {
featureBlockUsersEnabled()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: I could be wrong about this, but I don't think you need to do { featureBlockUsersEnabled() } - just featureBlockUsersEnabled should do the trick (though I think you need to pass the function as a normal param instead of trailing closure syntax). I'm okay with this as-is, but I think it's a little clearer when we don't make new closures just to call a single function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, you're correct. Will fix, that looks much nicer.


withEnvironment(remoteConfigClient: mockRemoteConfigClient) {
XCTAssertTrue(featureBlockUsersEnabled())
XCTAssertTrue(checkFeatureFlag())
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks good now!

@amy-at-kickstarter amy-at-kickstarter merged commit 9a7c871 into main Feb 1, 2024
6 checks passed
@amy-at-kickstarter amy-at-kickstarter deleted the feat/adyer/cleaner-feature-flags branch February 1, 2024 19:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants