Closed coletoncodes closed 11 months ago
Not sure if I can debug this based on what's given, but I will mention a couple of things, the most notable of which is...
override func setUp() {
super.setUp()
sut = OnboardingInteractor() // why is this created BEFORE reset and mock setup?
DIContainer.shared.reset()
DIContainer.shared.registerMocks()
mockUserStore = DIContainer.shared.userLocalStore() as? MockUserLocalStore
}
Generally you'd create the sut after setting up the mocks needed. As is, and since @Injected resolves immediately, I'd expect you to be getting the original objects.
One other minor nit, in this code
self.userStore
.userUpdatePublisher
.receive(on: DispatchQueue.main)
.sink { updatedUser in
self.state.user = updatedUser
}
.store(in: &cancellables)
You have a strong retain cycle in the sink. Should be sink { [weak self] updatedUser in self?.state.user = updatedUser }
If this doesn't help you'll probably need to create a baby project that demonstrates the problem.
That said, most issue of this type are due to timing. Breakpoint the unit tests and Factory registration closures and see when what is being created.
My goodness, I did actually fix the strong retain cycle in the sink shortly after updating this, but you are correct.
When I moved the SUT instantiation to the end, and added the .once() call for the factory my tests are now passing and behaving as expected.
I knew it was something simple, but only just now discovered the .once() modifier (great addition by the way).
We can consider this issue closed. Silly me. Thanks!
None of those registrations look like they should require once.
I've read the docs a few times, and have tried a few different approaches for my UnitTests. I'll share my current approach, and perhaps I'm missing something.
I have created a SharedContainer named DIContainer, it looks like so:
I'm having this issue with all of my tests, but for simplicity I will only be discussing the OnboardingInteractorTests.
For context, this Interactor is injected into my RootView's ViewModel like so:
I realize this isn't a normal architecture and the self.state is not explained here, but essentially this configure() method is called when the ViewAppears, and there is a middle layer called a "ViewState" which is what the View uses to draw it's content and is Published via @State.
UI Architecture aside, my RootView checks which view to present based on the User's onboarding state.
So almost immediately at app launch this injected service is used and the view is drawn.
In my normal workflow for UnitTesting I use "stubs" that are force unwrapped so that in that stub I can define the UnitTest functionality and make my assertions accordingly.
However, given the way Factory resolves its dependencies, I cannot use force unwrapping for this use case. Which is acceptable and makes sense to me. Not a major issue.
So I've adapted to be like so:
Okay, prerequisites are provided for my Application. Now on to the UnitTesting setup.
For the tests related to the OnboardingInteractor class, I have this:
Now my registration for the mock interactor is not being updated, and in fact is still cached between test runs.
Logs:
In my UnitTest, it appears that the original stubs are not being recreated and the original MockUserLocalStore's stubs are always returned, even though I'm setting the closure to perform differently in my test.
Which sort of makes sense, as the object that is returned is the "cached" object, based on the above logs. However, I'm resolving the dependency based on the Testing documentation and it should operate as I expect.
As far as I can tell I have followed the Documentation accordingly, and after a few days of trying a few different approaches I've ran out of solutions and am hoping I am missing something simple and not exposing a bug.
A few of the things I've tried:
Any suggestions are greatly appreciated, and hopefully I have provided enough information.