https://kotlinlang.org logo
Title
a

Alan Lee

07/10/2018, 9:44 PM
What you think about having additional parameterized constructor that would be used to pass in mock objects for unit testing only? Would you consider it a bad design since test only code is being inserted to production code? I’m working on a very small project and do not want to add the complexity of a dependency injection framework and thinking about injecting mocks into the class this way only when doing unit testing. Wondering if people find this as a bad design. Another way would be
@VisibleForTesting
annotation to assign mocks to variables directly but I find injecting mocks via the constructor is easier to read and clarify which objects needs to be mocked.
s

snowe

07/10/2018, 9:54 PM
what do you mean, pass in mock objects for unit testing only? generally when you are mocking things you only mock what is passed into the constructor in the first place. why would you need to add a separate parameterized constructor?
a

Alan Lee

07/10/2018, 10:17 PM
Well I create an instance of classes that I need in the class itself and do not need to have it passed in. I want to replace this with mock objects during testing.
s

snowe

07/10/2018, 10:32 PM
can you post a code example?
a

Alan Lee

07/10/2018, 10:32 PM
class OtherClass(var s: String)

class BeingTested {

    lateinit var otherClass: OtherClass

    init {
        if(!::otherClass.isInitialized)
            otherClass = OtherClass("normal")
    }

    constructor()

    constructor(other: OtherClass) {
        this.otherClass = other
    }

}

var normal = BeingTested()
var mocked = BeingTested(other = OtherClass(("mocked")))

print(normal.otherClass.s)
print(mocked.otherClass.s)
Here is a sample.
normal would print normal and mocked would print mocked
s

snowe

07/10/2018, 10:34 PM
why does your class not take it in the original constructor?
a

Alan Lee

07/10/2018, 10:36 PM
I can change it so it could do that assigning a default value. OtherClass does not need to be passed in. I just want to change it to a mock when testing.
class OtherClass(var s: String)

class BeingTested(var otherClass: OtherClass = OtherClass("normal")) {
}

var normal = BeingTested()
var mocked = BeingTested(otherClass = OtherClass(("mocked")))

print(normal.otherClass.s)
print(mocked.otherClass.s
Here is a simplified example that uses a default parameter. The point of my question is since I only use it to pass in a mock object would you consider this a bad design?
BTW. Thanks for taking the time to look at this.
s

snowe

07/10/2018, 10:40 PM
I think that if you are using an internal class like that and you need to mock it then something else is designed wrong. Here are the questions I would be asking myself (modifying production code to make it testable indicates a code smell to me)
1. Do you need this internal class?
2. Why do you need it?
3. Why can't it be passed in?
4. Can a setter be used instead of an overridden constructor?
a

Alan Lee

07/10/2018, 10:42 PM
1. yes. 2. It is required to implement feature for the given class 3. the external class or code does not know or doesn’t need to know about the OtherClass
4. Setter is also possible but again same question applies as it will be just used for testing.
s

snowe

07/10/2018, 10:43 PM
if external classes don't need to know about it then why do you need a mock?
it seems there's a bit of an XY problem going on here, maybe if you describe the actual problem I can better help.
a

Alan Lee

07/10/2018, 10:45 PM
Unfortunately I cannot share the actual code. So there are some dependency and since the OtherClass is not what I want to test and also it is bit problematic to run. Let’s just say it does some network requests so would not want to run it in a unit test.
s

snowe

07/10/2018, 10:46 PM
sounds fine, it seems like you should be passing it in as a parameter though, not just for testing, but always. That way you can switch it out if you want.
You don't need to write/use a DI framework for it though
just pass it in in all the places you use it.
a

Alan Lee

07/10/2018, 10:47 PM
Thanks. I could take various approaches to this but just wanted a quick opinion on the above approach from a good design perspective.
s

snowe

07/10/2018, 10:48 PM
You can also just use the 'Simple Factory' pattern. Create a wrapper class that instantiates your above class and passes in the proper OtherClass.
a

Alan Lee

07/10/2018, 10:50 PM
👍 Thanks for the link.
s

snowe

07/10/2018, 10:52 PM
np