-
Notifications
You must be signed in to change notification settings - Fork 120
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
Crash when calling mocks from dealloc
that are not called from elsewhere
#141
Comments
FYI, we've actually written an OCMockito helper that tracks all mocks that are created and automatically calls i.e. as well as I guess it would have to be an "add-on" though - some users may like the flexibility of stopping mocks at certain points in their tests. |
@cerihughes I'd be interested in |
@jonreid: Agreed - I think
Our automatic solution for tracking mocks isn't that smart... we just define our own Thanks! Cer |
I've spent some time investigating this more thoroughly and have realised that even with the PR #140 the problem hasn't gone away. I wrote a rough test case that captures all of the different scenarios for mocks being used in There's 1 simple scenario that even with the current codebase, is impossible to prevent from crashing:
In this test, there's no way I can see of guaranteeing that the mock object is disabled and stopped before deallocation of the code under test without the tester explicitly knowing about this problem, understanding the problem, and writing the test in such a way that the mock is stopped before the code under test is deallocated. IMO, that's too big an ask for a test writer. The crux of the problem is that
In this scenario, one of the arguments in the invocation is the object being deallocated, so we increase the retain count on this object during deallocation. When we then go to tear down the mock, we release it again, triggering a deallocation of an already deallocated object. So, how do we get around this? I had a couple of ideas:
I made a quick and dirty attempt at the 1st solution using the existing logic in The second solution seems more achievable but we'd need to introduce the concept of a OCMockito "session". This could be done with functions similar to the existing API, e.g. Of course, this also opens up lots of potential for things to go very badly wrong. What if a test calls At this point, it might be worth reverting #140 as it only solves part of the problem, and also introduces a change to the API. I'd be very interested in working with you on this, but it's probably best to talk about the direction you want the API to take first. Are you comfortable with the concept of "starting" and "stopping" mockito (i.e. a more memory-managed solution), or do you think there's more value trying to figure out a solution that can detect if Finally, some good news: I ran the same tests using OCMock and it failed in exactly the same way :) |
... or of course, the other option is to just document these corner cases and make sure testers know how to fix their tests when the crash occurs. |
That's an interesting piece of information about OCMock! That helps nudge my opinion toward your last comment: "fix with documentation" rather than trying to avoid the problem in OCMockito. |
Would you be willing to add a section to https://github.com/jonreid/OCMockito/wiki/OCMockito-FAQ ? |
I would like to roll another release. @cerihughes how do you feel about
|
Hey @jonreid! I think the latter... it hasn't made the problem go away, so let's get rid of it. I'll write something for the FAQ when I get a chance, but it might not be for a week or so. Thanks! |
hey @cerihughes! Any news on progress of this issue / pull request? If you've made any progress that would be awesome! |
I'm interested in the idea of something that can register when mocks are created. Here's my main design goal: It should be optional. That is, existing tests should work fine without this mock-cleaner-upper. One way of achieving this would be to create a subclass of XCTestCase. When mocks are created, they can notice their context and register themselves. Cleanup would happen automatically on Thoughts? |
Hey @RuudPuts ! No progress from me I'm afraid. We've pretty much decided to not use mocks to test any scenarios showing the |
@jonreid - I'm a little hesitant about putting too much logic in Maybe a better compromise would to to provide all the logic outside of the |
@cerihughes That makes sense. I wonder if we can make something so that: when a new mock is created, it can determine whether or not this list of of managed mocks even exists. If there is no list, do nothing. If there is a list, register itself. Something like that. |
I do agree on the mock tracker being optional, although I do think any test suite can benefit from it. Placing it in XCTestCase itself could cause problems when combining with other frameworks. An approach I've taken now is in a XCTestCase subclass define new my_mock() my_mockProtocol() etc methods, which create the mock using OCMockto's mock() mockProtocol() and store the mock created in the mock-cleaner-upper. From the teardown of the XCTestCase then the mock-cleaner-upper is called upon to cleanup all mocks. I've tried to achieve the same with a test observer, although I didn't get that to work unfortunately. @cerihughes I'm going to see if I can discover and fix the crashes still present after this pull request, although I can't seem to reproduce them always. Any insights you might still have I'd appreciate! |
As of Xcode 9, this could maybe be done without subclassing |
Hi Jon,
We're running into some issues where mocks injected into some code under test cause problems if the mock is called from the dealloc method AND ONLY the dealloc method.
Consider the following code under test:
Note that in the
init
method, we observe the 1st observable, but not the 2nd. Indealloc
, we stop observing both observables as we're not sure whetherdoSomethingLater
will have been called at this point.In a test, if I mock both observables and pass them in, the order in which I call
stopMocking()
suddenly becomes very important:observableObject2
thenobservableObject1
works just fine, asdealloc
won't be called untilobservableObject1
is stopped, so by the time we calldealloc
, all mocks are stopped, and the calls toremoveObserver:
on them do nothing.observableObject1
thenobservableObject2
causes a crash asdealloc
is called as soon asobservableObject1
is stopped and the call toremoveObserver:
onobservableObject2
then creates a strong reference from the mock to the object being disposed. When we stopobservableObject2
, the crash manifests as we'll try to release an already released object.Of course, this can be solved by making sure we stop the mocks in the correct order, but the test then has to be written with explicit knowledge of the implementation, and as the implementation changes, we may have to review the "stop order" to prevent further crashes. This doesn't strike me as a good thing!
I've created a PR (#140) which addresses the problem by separating the process of preventing the mock from receiving any more invocations (I call this "disabling" the mock in the PR), and stopping the mock. The downside is that tests now have to first disable, then stop all mocks to guarantee that this kind of crash won't happen.
Another option would be for OCMockito to ignore all invocations on a mock that come from the
dealloc
method.WDYT?
The text was updated successfully, but these errors were encountered: