2

I have an example object:

object Foo {
  private def sayFoo = "Foo!"
}

And I want to test the private sayFoo method without the following workarounds: 1. Without defining it as package private 2. without defining it as protected and than inheriting it in the test class

I saw one possible solution which is to extend privateMethodTester but it doesn't seem to work for objects just classes.

Now, I know that some, if not many, say that you are not suppose to test private methods but rather only public ones (api). Nonetheless, I still do want to test them.

thx in advanced

Mike Stockdale
  • 5,256
  • 3
  • 29
  • 33
Eyal Peleg
  • 87
  • 3
  • 9
  • scalatest has an ugly support: http://www.scalatest.org/user_guide/other_goodies#privateMethodTester – ayvango Oct 27 '15 at 19:22

3 Answers3

4

It cannot be tested by any standard means.

However, if you need to "cover" that section of code, then test the methods which use that private method in their implementation. It is indirect, granted, but it does effectively test the code.

For example, in your class you could have another method:

def dontSayBar = {
    sayFoo()
    println("Other processing...")
}

and this will "cover" the code in the private method if tested.

P.S. A good rule of thumb is that if you see code that isn't showing up in a coverage run, then you probably don't actually need that code.

John Starich
  • 619
  • 11
  • 15
  • i have several methods which I are invoked in the next manner all of which are privates: ...f(g(h(x))) where only h is public, therefore this solution would not fit since I wouldn't have a way to specifically test f for example. – Eyal Peleg Oct 27 '15 at 19:37
  • 1
    If you test f, then you are therefore testing g and h as well. The tests you write in [TDD](https://en.wikipedia.org/wiki/Test-driven_development) are to test accessible APIs. If the private method isn't tested to your standards, then you will have to find a way to run into that code using your publicly accessible APIs. If you don't run into that code via public APIs then it probably isn't worth holding onto. In the case of a class, non-private methods are the only one's tested. A private method, if used, will be covered by these tests. If it isn't, then you probably don't need it. – John Starich Oct 27 '15 at 19:47
  • Testing via reflection, as done in the other answers, is possible but **highly** discouraged for complexity reasons. Usually testing is considered complete if you reach full code coverage but not necessarily full method coverage. – John Starich Oct 27 '15 at 19:56
2

As other said, there is no way to directly test a private method, especially of an object. However you can cheat by using reflection in order to bypass the restriction.

I'm not sure if there is a cleaner way to do the same using Scala reflection API, however the following code using Java's Reflection does work:

val m = Foo.getClass.getDeclaredMethod("sayFoo")
val inst = Foo.getClass.getField("MODULE$").get()
m.setAccessible(true)
m.invoke(inst)

First you need to get a reference to the sayFoo method. Next you need to obtain the reference to the singleton instance of the Foo object which is saved in the MODULE$ static field.

Once you have both references you just need to tell the JVM to remove the access restriction from the method, making it accessible.

This is clearly an ugly hack which, but if used sparingly and only for testing purposes may be useful.

nivox
  • 2,060
  • 17
  • 18
1

Probably someone has done this already: How about an annotation macro @Testable that emits the object with a method that exposes the private members, but only when compiling for testing (and just the object otherwise).

scala> object X { private def f = 42 ; def g = 2 * f }
defined object X

scala> X.g
res1: Int = 84

scala> object X { private def f = 42 ; def g = 2 * f ; def testable = new { def f = X.this.f } }
defined object X

scala> X.testable.f
warning: there was one feature warning; re-run with -feature for details
res2: Int = 42

scala> object X { private def f = 42 ; def g = 2 * f ; def testable = new Dynamic { def selectDynamic(name: String) = X.this.f } }
defined object X

scala> X.testable.f
warning: there was one feature warning; re-run with -feature for details
res4: Int = 42

Alternatively, it could emit a different object TestableX.

som-snytt
  • 39,429
  • 2
  • 47
  • 129