How to test protected function


#1

In Java protected members are also visible in the whole package and thus could be tested right away.
In Kotlin however, protected members are only visible in subclasses.

How are they supposed to be tested in unit tests?
From my point of view, protected members are part of the API and really should be tested.


#2

Subclass your class and call super.


#3

That works, of course.

But that means your test class needs to extend the class under test which is quite unusual in my opinion.


#4

As you said, protected functions are part of the API, so it makes sense to test them.
IMO, it makes sense to test them the way they are going to be used.


#5

It would look like this:

open class A {
    protected fun f() = 3
}

class ATest : A() {
    @Test fun testF() {
        val testee = ATest()
        assertEquals(3,testee.f())
    }
}

Maybe its just me, but I find it strange that the “testee” is the test class itself…


#6

As far as I know nothing is stopping you from doing something like this:

open class A {
    protected fun f() = 3
}

class B: A() {
    public fun fPublic() = f()
}

class ATest {
    @Test fun testF() {
        val testee = B()
        assertEquals(3,testee.fPublic())
    }
}

which gives you sepration of tested code form code of the test.


#7

Well, to be honest, thats exactly how I did it.
But I didn’t like it, creating a wrapper class and a wrapper function.
So I asked myself, if I was missing something…

Thanks anyway for all your comments!


#8

I’m having this same issue. I understand that you can extend the class when it’s open or abstract (as shown in comments above), but what about when it’s final?

Sure, you can change your class to be open because you want to test the protected methods inside it, but then your code architecture is influenced by the tests, which I don’t like. The whole reason why Kotlin classes are not open by default is so that we can make them open only when it makes sense, and for me, it doesn’t make sense to make it open just to test it.

Is there any other option?


#9

If you have a final class, protected members don’t make any sense (and I hope are rejected by the compiler). If you want to test internal methods, that’s another matter.


#10

Hi Hugh,

I agree with that in most cases, but I have a scenario where this actually makes sense, e.g.:

abstract class A {
    public fun doStuff() {
        doSomething()
    }

    protected abstract fun doSomething()
}

class B : A() {
    override fun doSomething() {
        ...
    }
}

and what I want is to create a unit test for B.doSomething()


#11

I guess one thing you could do is use the all open compiler plugin while testing and disabling it when deploying. It should be possible to set something like that up using gradle/maven. The only problem is that you no longer get any errors when inheriting from a sealed class during development and testing. Not sure if this is really the best thing to do, but if you want to check internal methods thats the only idea I have.


#12

Thanks for the recommendation Wasabi375! I was not aware of that plugin. I will look into it in more detail when I have time, but if it’s too much work to set up only for testing (without affecting the development compilation) then I probably won’t use it.

For the moment what I did is set the protected methods I need to unit test as internal and added a TODO so I can make them protected once I find a better solution. It’d be great to get some official support for this, maybe a plugin like the one you mentioned but that changes the visibility of methods or classes only for testing.


#13

Ah, I see: you want to test final overrides of a protected method. In that sort of case, I wouldn’t try to test the protected method directly. Instead, I’d do two things.

  1. Write a test which uses a spy subclass to check that the base class is calling the protected method as it should. (For example, if the base class should cache the result, then base method calls after the first shouldn’t reach the subclass.)
  2. Write an abstract test for the base class and override it for each subclass which needs to be tested. The test subclasses would change the setup and/or add more tests to cover the effects of the method overrides.

Alternatively, change from inheritance to composition - pull out the overridable parts into a Strategy object, and test those Strategies as separate classes.


#14

Thanks for the help @HughG :slight_smile:

Would you mind giving a short example of point number 2? I’m not sure I understand what you meant exactly and a short code example would be greatly appreciated.

Changing from inheritance to composition would certainly make testing easier. Unfortunately, given the nature of the relationship between the classes, using composition would make the code more complicated and it’d make less logical sense.