Reputation: 73
This is not how my code looks like, but it will come close to what i want as an example
class Phone {
public makeCall(String number) {
addEntryToCallLog(number)
//eventually make a call
}
private void addEntryToCallLog(String number) {
//make database calls
}
}
class PhoneTest {
Phone phone
@Test
public void canMakeACall() {
//mock the behaviour of phone.addEntryToCallLog to prevent database exceptions
phone.makeCall("1223");
//Assert somehow that call was made << --IGNORE this NOT IMPORTANT
}
}
I want to test "makeCall" in a unit test, but when i do so the code would throw some database exception and hence break my test. I think it is reasonable to be able to test mock the behaviour of java private methods since this allows for a consistent behaviour for all tests.
I have used groovy in the past and with it i could use spock to mock the behaviour of private methods. I could also use metaClasses to do the same for instances I create. But in java there doesnt seem to be a straight forward way to do this.
I have also tried mockito and power mockito but they allow me to change the return value of private methods, but they dont allow me to change the behaviour.
This seems to be an obvious thing that someone out there has had ti deal with. I suppose I am missing something. But what is it.
Upvotes: 1
Views: 2649
Reputation: 95654
My recommendation is to relax the visiblity from private
to package-private, possibly with a @VisibleForTesting
annotation or /** Visible for testing. */
note. At that point you can "partially mock" your internal method using doAnswer
, as DTing mentions in the other answer, to replace the real implementation's behavior.
The main problem here is that Mockito is best at turning interfaces into stub implementations. Private methods, however, are very deliberately not a part of an interface, and thus Java makes them harder to access through some of the reflective features that plain Mockito relies on. (Behind the scenes, Mockito is creating a subclass of the class you're mocking, and subclasses normally can't override private methods.)
You should also remember that your unit tests are ideally supposed to test your class as a unit, without delving into the implementation details. By saying that you're trying to replace the behavior of your private method, you're signalling that your class need a seam that doesn't yet exist. You might consider refactoring for dependency injection:
class Phone {
interface CallLogger {
void addEntry(String number);
}
private final CallLogger logger;
public Phone() {
this(new DefaultCallLogger());
}
/** Visible for testing. */
Phone(CallLogger logger) {
this.logger = logger;
}
/* ... */
}
...which would then let you pick any CallLogger you want, including going to the database in production, faking or mocking it in a test, or replacing it with a batching version in the future. Likewise for making addEntryToCallLog
package-private or protected
: You're indicating that subclasses can change how they add entries to call logs, which is exactly what you're trying to do in the test, and admitting that will help the Java VM and your code's readers to understand at what points the behavior can change.
All that said, if you merely want to use PowerMockito to replace the implementation, you can use PowerMockito's doAnswer
and when
to provide different behavior for your private method. Just remember that you have an opportunity to improve the design and spend less time and effort fighting the implementation details.
Upvotes: 2
Reputation: 39287
Your example is strange because your getContactFromDatabase doesn't return anything but you use it to set a Contact variable. If you want to mock out object creation behavior, have a look at this:
https://code.google.com/p/mockito/wiki/MockingObjectCreation
class PhoneTest {
@Spy Phone phone;
@Test
public void canMakeCall() {
doReturn(someContact)
.when(phone)
.getContactFromDatabase(someString);
phone.makeCall(someString);
}
}
If you want to do nothing:
class PhoneTest {
@Spy Phone phone;
@Test
public void canMakeCall() {
doNothing()
.when(phone)
.getContactFromDatabase(someString);
phone.makeCall(someString);
}
}
http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html#13
You can create spies of real objects. When you use the spy then the real methods are called (unless a method was stubbed). Real spies should be used carefully and occasionally, for example when dealing with legacy code.
Upvotes: 1