-
Notifications
You must be signed in to change notification settings - Fork 118
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
[Bug] Fix crash and add unit test to cover the issue. #1080
Merged
Merged
Changes from 2 commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Awesome! Thanks for adding tests! 🙇🏼 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package org.wordpress.aztec | ||
|
||
import io.mockk.every | ||
import io.mockk.mockk | ||
import org.junit.Assert | ||
import org.junit.Test | ||
import org.junit.runner.RunWith | ||
import org.robolectric.RobolectricTestRunner | ||
import org.wordpress.aztec.spans.MarkSpan | ||
|
||
@RunWith(RobolectricTestRunner::class) | ||
class MarkSpanTest { | ||
/** | ||
* Test used to confirm two crashes related are fixed. | ||
* | ||
* https://github.com/wordpress-mobile/WordPress-Android/issues/20738 | ||
*/ | ||
@Test | ||
fun `Calling MarkSpan#safelyParseColor with empty string should not cause a crash`() { | ||
var error = false | ||
var result: Int? = null | ||
try { | ||
val span = mockk<MarkSpan>() | ||
every { span.safelyParseColor("") } coAnswers { callOriginal() } | ||
thomashorta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
result = span.safelyParseColor("") | ||
} catch (e: Exception) { | ||
error = true | ||
} | ||
Assert.assertFalse(error) | ||
Assert.assertEquals(null, result) | ||
} | ||
|
||
/** | ||
* Test used to confirm two crashes related are fixed. | ||
* | ||
* https://github.com/wordpress-mobile/WordPress-Android/issues/20694 | ||
*/ | ||
@Test | ||
fun `Calling MarkSpan#safelyParseColor with null string should not cause a crash`() { | ||
var error = false | ||
var result: Int? = null | ||
try { | ||
val span = mockk<MarkSpan>() | ||
every { span.safelyParseColor(null) } coAnswers { callOriginal() } | ||
thomashorta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
result = span.safelyParseColor(null) | ||
} catch (e: Exception) { | ||
error = true | ||
} | ||
Assert.assertFalse(error) | ||
Assert.assertEquals(null, result) | ||
} | ||
} | ||
thomashorta marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 believe this doesn't need to be
internal
to be testable, since the public constructor on line 24 already callssafelyParseColor
on acolorString
passed as argument.I didn't check the code further so I don't know if the
super
does something that's not supported at runtime, so if that's the case, feel free to ignore this comment. Otherwise I believe this could still beprivate
and the unit test can just instantiateMarkSpan
with empty and null strings to test the fixed scenario.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 think there is a misunderstanding. I marked it
internal
to decrease the visibility. Went from private to internal. Protected wouldn't work and I need it visible for testing. If you think it being public is fine, I can do that too. Or maybe there is a mockK feature that gets around this annoyance, let me know.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 I meant is that even keeping this
private
, which I agree we should opt for the most restrict visibility we can here, you can test this method by calling the constructor ofMarkSpan
that receives a color String as an argument, then check in that created instance ifgetTextColor
returns what you expect + the constructor doesn't throw any exceptions.In other words, treating the class as a black box and testing behaviors, inputs, and outputs, instead of making a specific method visible for testing and white-box testing its source code.
Besides that, I think I anticipated myself a bit in my first comment mentioning
super()
, but what I meant is that I see that this class is a subclass and I didn't check the constructor of the superclass to affirm that calling the constructor ofMarkSpan
from a test can cause any issues, in which case it could make sense to bump the visibility tointernal
so we can at least test something.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.
Great point. And I think we may be getting into the weeds here. What I did was on purpose and I specifically meant to test just a method.
When I approach unit tests I try and test a very specific unit of code. What if the implementation of one of the constructors changes? For example adding a new required parameter? The test could break making it brittle. If we test a specific method, it's independent of any business logic changes since we are testing just a function. And more importantly, we really only care about the color parsing which is what this crash is about.
Additionally we would no longer have the ability to check the return type ofI missedsafelyParseColor
. We'd need to in the end exposetextColorValue
, which in essence is equivalent to exposing the methodsafelyParseColor
.getTextColor
. And that makes sense.With all that said, it sounds like you feel strongly about running the test a specific way so I made the changes that I think you wanted. I've never used MockK, so let me know if there are any further 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.
This is a valid point as well!
I tend to approach test with the class usage in mind, treating everything private as implementation details, which are subject to change at any time. Or as many say: "test behavior, not implementation". The testable "units" for me are any public APIs an external code can use to interact with the class.
For instance, in this situation, in the app perspective, the crash happened when instantiating
MarkSpan
(which in turn calls private/internal methods to initialize and set up itself). The calling code only interacts with the constructor and with thegetTextColor
public method, so it makes sense to call and assert using only them.But what you said is totally valid as well, one might be more interested that each function inside a class work as expected, especially if they are more like scoped utility functions that are used several places inside that class. And that works fine especially in lib code (like this) and modularized projects, where
internal
helps with keeping things private to external callers.Anyway, I think this was a nice discussion and I personally think the code looks cleaner now in this last iteration, so I will just reiterate my approval for this last code review.
Thanks for the changes! 🙇🏼