53

I have a companion object with a private method, like so:

package com.example.people

class Person(val age: Int)

object Person {
  private def transform(p: Person): Person = new Person(p.age + 1)
}

I would like to test this method, with something like:

class PersonSpec extends FlatSpec {

  "A Person" should "transform correctly" in {
    val p1 = new Person(1)
    val p2 = Person.transform(p1) // doesn't compile, because transform is private!
    assert( p2 === new Person(2) )
  }

}

Any help on having test code access private methods?

Actually, as it is written, I might be able to create a subclass of Person, but what if Person is declared as final or sealed?

Thanks!

gdiazc
  • 2,108
  • 4
  • 19
  • 30
  • 3
    Make it package-private, put your testing code in the same package, under test root. – om-nom-nom Jan 26 '14 at 21:12
  • 5
    Unit tests test that classes conform to their contracts. Private methods are not part of that contract and are not subject to the class's invariants. It's my opinion that private methods shouldn't be (directly) tested. – Randall Schulz Jan 26 '14 at 21:13
  • 4
    Ok, agreed. But let's say I have a complex data structure and the ``transform`` method implements a specific algorithm over that data structure. I don't want that algorithm to be exposed in the API, and yet I need it to work in every corner case. Where should I place that ``transform`` method in order to be able to test it exhaustively? – gdiazc Jan 26 '14 at 21:29
  • Why is that algorithm not available in the API? – Randall Schulz Jan 26 '14 at 22:50
  • Why not just make `transform` a public method in `Person`? It doesn't leak any abstraction of the algorithm. – Vidya Jan 26 '14 at 23:28

8 Answers8

89

I am in the middle when it comes to testing everything. I don't usually test everything, but sometimes it's really useful to be able to unit test a private function without having to mangle my code to make it possible. If you're using ScalaTest, you can use the PrivateMethodTester to do it.

import org.scalatest.{ FlatSpec, PrivateMethodTester }

class PersonSpec extends FlatSpec with PrivateMethodTester {

  "A Person" should "transform correctly" in {
      val p1 = new Person(1)
      val transform = PrivateMethod[Person]('transform)
      // We need to prepend the object before invokePrivate to ensure
      // the compiler can find the method with reflection
      assert(p2 === p1 invokePrivate transform(p1))
    }
  }

That may not be exactly what you want to do, but you get the idea.

Stereo
  • 1,148
  • 13
  • 36
smashedtoatoms
  • 1,648
  • 14
  • 19
  • 14
    `import org.scalatest.{ FlatSpec, PrivateMethodTester }` is the import. I hate having to hunt that crap down and then I forgot to put it in the answer. – smashedtoatoms Jun 23 '14 at 22:46
  • I gave you an upvote because this looked so great, but then found out that it doesn't work for me. My test fails with `org.scalatest.PrivateMethodTester$Invocation@13838550 did not equal (-2,-1,1,2)`. Has the API changed since you wrote this, or am I doing something wrong? How do I get my actual result out of the method invocation? – rumtscho Jul 04 '14 at 08:55
  • Yea, I actually botched the example. You have to use the invokePrivate function to actually call the function. In this example you would say `assert(p2 === invokePrivate transform(p1))` [Here](http://www.scalatest.org/user_guide/other_goodies#privateMethodTester) is an example. Sorry about that. – smashedtoatoms Jul 05 '14 at 16:51
  • I updated my answer to have the invokePrivate function in there. I feel really bad about that. I hate it when I get on here and get 1/2 an answer, so hopefully this helps you out. Take care and good luck. – smashedtoatoms Jul 05 '14 at 16:57
  • @jiegler thanks for the update. I found the correct API yesterday and tried it, but for some reason it didn't work either. I have the import, but it still says that my object does not have the invokePrivate method, http://stackoverflow.com/questions/24571260/scalatest-cannot-call-invokeprivate. Still, I'm keeping the upvote, because my problem seems to be a fluke. – rumtscho Jul 05 '14 at 17:00
  • Oh, I've had that problem too. Just make sure you extend your test class with the org.scalatest.PrivateMethodTester trait. The PrivateMethodTester is what gives you your invokePrivate method. I'll respond on your link. Good luck. – smashedtoatoms Jul 05 '14 at 20:45
  • 7
    The code didn't work for me, until I realized I should add the class name before `invokePrivate`, i.e. `Person invokePrivate transform(p1))` – Dotan Apr 26 '17 at 14:45
  • Can it be used to test `curried` *private*-methods too? – y2k-shubham Jun 11 '18 at 10:03
  • +1 for the answer. it directed me to the PrivateMethodTester. Here's an example from the scalaTest's documentation – mahmoud mehdi Sep 13 '18 at 12:22
  • 2
    How would one do the same thing for an `object` ? – Wonay Jan 03 '19 at 21:05
  • you actually need to use the instance: `p1 invokePrivate transform(p1)` – Kevin Won Oct 15 '19 at 22:16
  • I need to test a private function in an object, say `private def myFunc(a: Int, b:Int): Boolean = {...}` in object `object myobject {...}`. How can I use `invokePrivate` with that? – Vedang Waradpande Oct 16 '19 at 06:37
  • Nevermind, I found the answer: its `myobject invokePrivate myFunc(a, b)`. – Vedang Waradpande Oct 16 '19 at 06:41
  • It is just gives me "java.lang.IllegalArgumentException: Can't find a private method named: xy " 100% of the time – beatrice Sep 01 '21 at 15:58
39

You could declare your method to be package private:

private[people] def transform(p: Person): Person = new Person(p.age + 1)

If you put PersonSpec in the same package it will be able to access it.

I leave it to you to decide if it's really wise to unit test a private method :)

vptheron
  • 7,426
  • 25
  • 34
  • 2
    Technically true of course, but I think that broadening access for the sole purpose of making testing easier (rather than serving the needs of your clients) is a bad idea and hints something is amiss. In that sense, I get a little closer to @Peter's position of design smell. – Vidya Jan 26 '14 at 21:46
  • @Vidya SO is designed to host technical questions/answers I believe, other stackExchange websites are meant to host design/best practices questions. – vptheron Jan 26 '14 at 22:52
  • 3
    That's true if the question is "Should I test private methods or not?" In this case, the OP asked a truly technical question based on a premise that many find faulty, and it is fair to point that out. After all, the best way to solve a problem is prevent it from happening. I would also point out [this SO thread on the same topic](http://stackoverflow.com/questions/34571/whats-the-proper-way-to-test-a-class-with-private-methods-using-junit) was considered so valuable it was archived for prosperity. So there is a precedent for this kind of discussion. – Vidya Jan 26 '14 at 22:57
  • 1
    @Vidya it is not archived and was asked in the early SO times (2008), when moderation and site scope wasn't so tight so it's a bad argument for precedence. See also meta on [historical locks](https://meta.stackexchange.com/questions/126587/what-is-a-historical-lock-and-what-is-it-used-for) – om-nom-nom Jan 26 '14 at 23:58
  • There are numerous entries in that thread made in 2012, so I don't know where you pulled 2008 from. That's like an entire American Presidential term of difference. Or an Olympics. Anyway, please enlighten me. When was the magical inflection point when SO went from the unbridled chaos of [The Hunger Games](http://www.imdb.com/title/tt1392170/) to the strict discipline of [Elysium](http://www.imdb.com/title/tt1535108/)? I will make sure to only cite posts from that point on. – Vidya Jan 27 '14 at 00:13
  • I usually don't need to test private methods, but this was helpful when I needed to mock some functionality that was in a private method used by other methods being tested. – Lilith Schneider Sep 17 '20 at 19:06
6

The need to unit-test private methods is a design smell.

Either you test them through your public API which is ok if they are small and just helper methods - or, which is more likely, it contains different logic/responsibility and should be moved to another class that is used by delegation in the Person. Then you would test the public API of that class first.

See a related answer for more details.

Likely you can access it using Java/Scala reflection, but it is just a workaround for the design problem. Still, if you need to, see a related Java answer how to do that.

Community
  • 1
  • 1
Peter Kofler
  • 9,252
  • 8
  • 51
  • 79
  • 61
    I think it is a code smell if you aren't testing your private methods. A unit test should test the smallest unit of work in your application. What you are describing is more of an acceptance test against the API. You shouldn't have to go through your public API to test your private methods. – wizulus Sep 21 '15 at 16:38
  • It should be permissible to unit test private methods. Private visibility is to serve encapsulation of which a unit test counterpart is very much a part of. – Coder Guy Sep 01 '16 at 16:56
  • Interesting how many down votes this answer gets again and again, indicated by the comment by alancnet I guess. – Peter Kofler Jun 19 '17 at 20:43
  • 4
    Unit tests are useful to the developer during the act of changing code. The information that helps you is not an aggregated pass/fail condition bubbled up to higher levels of the API, but rather the information that helps you understand while changing *that* method is totally local to *that* method, and that's what should be in the test. While "just test it in the public API" is fine for bubbled up aggregated business concerns, it is not at all fine for day to day development and dense code changes. The feedback loop is too uninformative about why local changes in a private method broke tests. – ely Aug 17 '18 at 12:32
  • I agree with this response. If you need to test private method that means you're writing classes that have too much responsibility. Split it into smaller components that are naturally testable. – kiedysktos Sep 23 '22 at 12:41
4

@jlegler's answer here helped me, but I still had some debugging to do before things worked, so I thought I'd write exactly what's needed for this here.

to test:

class A

object A {
  private def foo(c: C): B = {...}
}

use:

val theFuncion = PrivateMethod[B]('foo)
val result = A invokePrivate theFunction(c)

Note the locations of A, B

Dotan
  • 6,602
  • 10
  • 34
  • 47
3

Personally, I say make everything public and just prepend with _ or __ to indicate that other devs shouldn't use it.

I realize this is Scala and not Python, but regardless, "We're all consenting adults here."

"Private" methods aren't actually private (for example) and certainly aren't secure, so why make life harder for what is essentially a social contract? Prepend and be done -- if another dev wants to go poking around in dark places, they either have a good reason or deserve what they get.

MikeTwo
  • 1,228
  • 1
  • 14
  • 13
  • 1
    Does the organization deserve it? Privatizing something protects one developers bad decisions from the rest of the organization by preventing the developer from making the choice in the first place. – Brian Yeh May 06 '19 at 17:46
2

Generally speaking: if you want to effectively test your code, you first have to write it testable.

Scala implements the functional paradigm and extensively uses immutable objects by design, "case classes" are examples (my opinion: the Person class should be a case class).

Implementing the private methods make sense if objects has mutable state, in this case you might want to protect the state of the objects. But if objects are immutable, why implement methods as private? In your example, the method produces a copy of Person, for what reason do you want to make it private? I do not see any reason.

I suggest you think about this. Again, if you want to effectively test your code you have to write it testable.

Marco
  • 1,642
  • 3
  • 16
  • 29
  • "In your example, the method produces a copy of Person, for what reason do you want to make it private?" To not to clutter user API with methods he is not interested in. – MaxNevermind Jun 21 '17 at 11:19
1

a possible work around would be testing private method indirectly: testing a public method which calls the private method

Septem
  • 3,614
  • 1
  • 20
  • 20
1

I don't think that unit testing is about testing contract of the class - it is about testing simple functionality(unit).

Also I don't think that it is a good idea to make some methods public only to make them easily testable. I believe that keeping API as narrow as possible is a good way to help other developers to use your code(IDE will not suggest private methods) and understand contract.

Also we should not put everything in a single method. So sometimes we can put some logic into a private method.... and of course we want to test it as well. Testing it through the public API will increase complexity of you test.(other option is to move logic of the private method to another helper class and test it there..this class will not be used directly by developers and will not clutter up api)

Guys from scalatest ,I think, added PrivateMethodTester for a purpose.

Deil
  • 492
  • 4
  • 14