-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Fake Remote Config console for testing #5633
Conversation
settings.minimumFetchInterval = 0 | ||
config.configSettings = settings | ||
|
||
FakeFetch.config = ["Key1": "Value1"] |
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.
Initialize the fake console.
@@ -0,0 +1,28 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> |
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.
Dummy GoogleService-Info.plist
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
#import <FirebaseRemoteConfig/RCNFakeFetch.h> |
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.
Makes the RCNFakeFetch visible to Swift tests.
+ (void)setGlobalTestBlock:(RCNConfigFetcherTestBlock)block { | ||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000027", | ||
@"Set global test block for NSSessionFetcher, it will not fetch from server."); | ||
gGlobalTestBlock = [block copy]; |
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 feel like using a test block is simpler and doesn't take a lot of test code in production. And you can customize the block in test rather than having all the test logic in production code. You can also provide flexible return value (not just success) to test different scenarios.
This is used in a lot of the unit tests of remote config (search setGlobalTestBlock in tests), but I'm not sure the current state of how many of those tests are running but if we remove this, we might also have to adjust all those unit test as well.
Another thing is do we want to fake fetch in integration test since in github network is available. isn't it better to actually enable fetch from prod server?
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.
The test block is only used in disabled unit tests that still need to be ported from RC v1 to v2. This implementation allows the tests to be implemented without any knowledge of the library implementation details - just a simple API of managing a dictionary.
The swift_api tests are similar to these but do fetches from a prod server. This set of tests are to enable API tests that require midstream console changes.
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.
As for me, for unit tests I would prefer injecting another implementation of RCNConfigFetch
for testing purposes instead of adding the test code to production with either the test block or RCNFakeFetch
. For this we ideally should replace dependency on RCNConfigFetch
to a protocol dependency like RCNConfigFetchProtocol
or use a mock implementation for tests like here (less preferred). Anyway, we should allow passing RCNConfigFetch
as an init parameter for tests, then we can avoid the test code in production but keep the code testable.
For integration end-to-end tests, in contrast to unit tests, I would suggest not to mock anything and use real implementations only.
+ (void)setGlobalTestBlock:(RCNConfigFetcherTestBlock)block { | ||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000027", | ||
@"Set global test block for NSSessionFetcher, it will not fetch from server."); | ||
gGlobalTestBlock = [block copy]; |
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.
As for me, for unit tests I would prefer injecting another implementation of RCNConfigFetch
for testing purposes instead of adding the test code to production with either the test block or RCNFakeFetch
. For this we ideally should replace dependency on RCNConfigFetch
to a protocol dependency like RCNConfigFetchProtocol
or use a mock implementation for tests like here (less preferred). Anyway, we should allow passing RCNConfigFetch
as an init parameter for tests, then we can avoid the test code in production but keep the code testable.
For integration end-to-end tests, in contrast to unit tests, I would suggest not to mock anything and use real implementations only.
|
||
import XCTest | ||
|
||
class APITests: XCTestCase { |
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.
Just to make sure we are on the same page: are these tests supposed to test, RCNRemoteConfig
class implementation or integration of all remote config components? If the later, we should not mock remote config classes but rather Foundation classes, otherwise we would not tests the actual code.
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.
@maksymmalyhin These are intended to be as close as possible to integration tests while enabling changes to the console which is not possible in pure integration tests and a source of several bugs. The point of RCNFakeFetch is to enable a dictionary API for tests to simulate console changes.
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.
@maksymmalyhin I went with the approach of partially mocking URLSession. This seems to be a less-intrusive approach than injecting another RCNConfigFetch. Besides changing URLSession, it required a flag and two early exits from two RCNConfigFetch methods doing unrelated networking. PTAL
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.
This is a cool way to do this. It makes sense to me and doesn’t seem to leave much testing code in production. 👍
@@ -194,7 +194,7 @@ jobs: | |||
env: | |||
- PROJECT=RemoteConfig METHOD=pod-lib-lint | |||
script: | |||
- travis_retry ./scripts/if_changed.sh ./scripts/pod_lib_lint.rb FirebaseRemoteConfig.podspec --platforms=ios | |||
- travis_retry ./scripts/if_changed.sh ./scripts/pod_lib_lint.rb FirebaseRemoteConfig.podspec --platforms=ios --skip-tests |
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.
pod lib lint
tests don't work now that there's multiple RC test_specs. All tests are run in GHA.
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 Paul, that looks much better to me! It still seams we can avoid having a tests specific code in production (see comments).
@@ -204,6 +205,10 @@ - (NSString *)FIRAppNameFromFullyQualifiedNamespace { | |||
/// requests to work.(b/14751422). | |||
- (void)refreshInstallationsTokenWithCompletionHandler: | |||
(FIRRemoteConfigFetchCompletion)completionHandler { | |||
if (self.testWithoutNetwork) { |
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 wonder what makes this flag necessary? Would a test utils with mock dependencies like in FCM solve the issue without changes to the production code?
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.
Eliminated the testWithoutNetwork
flag by mocking refreshInstallationsTokenWithCompletionHandler:
. Mocking the dependencies seems more complicated and may be better to do in conjunction with a bigger refactor of RCNFetch.
@@ -111,6 +109,9 @@ - (instancetype)initWithContent:(RCNConfigContent *)content | |||
|
|||
/// Force a new NSURLSession creation for updated config. | |||
- (void)recreateNetworkSession { | |||
if (self.testWithoutNetwork) { |
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.
What if we inject implementation of newFetchSession
method (e.g. with a block) as instead? Or we can use OCMock to stub + [NSURLSession sessionWithConfiguration:]
method to avoid the class changes altogether.
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.
This one is also tricky, since we still need the first newFetchSession
to happen before any mocking. Thus, I went with the more expedient mock of recreateNetworkSession
until we do a bigger refactor.
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.
LGTM. I still can see room for improvements 😛, but nothing blocking from my point of view.
config.configFetch.fetchSession = URLSessionMock(with: fakeConsole) | ||
if !APITests.mockedFetch { | ||
APITests.mockedFetch = true | ||
config.configFetch = FetchMocks.mockFetch(config.configFetch) |
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.
Looks fine if we accept that RCNConfigFetch
is not properly verified by the test.
RCNConfigFetch *mock = OCMPartialMock(fetch); | ||
OCMStub([mock recreateNetworkSession]).andDo(nil); | ||
OCMStub([mock refreshInstallationsTokenWithCompletionHandler:[OCMArg any]]) | ||
.andCall(mock, @selector(doFetchCall:)); |
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.
Reliance on private methods can make refactoring a bit more painful in the future.
@@ -56,8 +53,8 @@ typedef void (^RCNConfigFetcherTestBlock)(RCNConfigFetcherCompletion completion) | |||
/// Add the ability to update NSURLSession's timeout after a session has already been created. | |||
- (void)recreateNetworkSession; | |||
|
|||
/// Sets the test block to mock the fetch response instead of performing the fetch task from server. | |||
+ (void)setGlobalTestBlock:(RCNConfigFetcherTestBlock)block; | |||
/// Provide fetchSession for tests to override. |
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 like this better!
XCTAssertEqual(status, RemoteConfigFetchStatus.success) | ||
self.config.activate { error in | ||
if let error = error { | ||
print("Activate Error \(error)") |
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'm guessing this should also be removed :)
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.
With the deprecated API whether or not there is an error on the first activate call is random depending on the state of the console - so it might be useful to keep until we can delete along with the deprecated API at the next major release.
I'm going to merge now to start lining up for M72. If there are more comments, I'll address in a subsequent PR. |
Add a FakeConsole test class along with a URLSession subclass for running Remote Config tests without networking and enabling console changes while testing.
Also delete the no-longer used globalTestBlock that was a more complicated fake for faking the fetch closer to the network call.