Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking/stubbing private variables of a class without getter and setter methods

I recently started working on TDD for exisitng project and faced couple of issues, one of those is mentioned below

I have a private variable to be mocked in test class and the variable looks like below

private Class<XYZ> cls = XYZ.class;

later this "cls" variable is used as arugment for one of the method as className

private List create(Class className, Object objectTO,   List<String> names)

I know private variables can be mocked and I mocked the private variable in my test case by following below steps

  1. Declared a java.lang.reflect.Field;
  2. Field field = PowerMockito.field(XYZ.class,"cls");
  3. field.set(XYZ.class, "objectOfXYZClass");

When i run my test class,i'm getting below error

java.lang.IllegalArgumentException: Can not set java.lang.Class field com.tools.XYZ.cls to java.lang.Class
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:164)
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:168)
at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:55)
at sun.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:75)
at java.lang.reflect.Field.set(Field.java:680)

Please can someone help me out on this and let me know what i'm missing.

PS: I used @preparefortest and mentioned all the required classes in that and running my test class using @runwith(powermockrunner.class)

like image 727
Praveen Kumar Mekala Avatar asked Oct 19 '22 03:10

Praveen Kumar Mekala


1 Answers

You are getting unit tests wrong. They are not about testing internal implementation (like fields or private methods).

Unit tests should typically do only the following things:

  1. You create an object of your "class under test"
  2. You call public methods on that object; and you "assert" on the results to those calls (that would include for example to expect certain exceptions here or there).

In other words: you very much want to only test on the externally visible behavior of your "class under test". And you know: if your class has no externally visible behavior - why do you have than in the first place?!

Of course, sometimes that isn't "good enough". In that cases, what you typically do: you turn to some mocking framework (like EasyMock or Mockito) to create mocked objects. You use these mocks when instantiating your class under test (called dependency injection). By doing so, you get full control over your class under test; for example you can verify that your mocks see those method invocations that you would expect.

So, long story short: yes, it would be possible to somehow access fields of your class under test. But that leads to unit tests that are not worth that name. Because those tests break as soon as you start refactoring the internals of your code. In that sense: such tests are only good to prove that your current implementation works as expected. But any time you change something, you will have to rework (probably larger!) parts or your test code.

Finally: writing unit tests is a skill that needs to be learned. One of the best resources to get there are those videos.

like image 199
GhostCat Avatar answered Oct 27 '22 09:10

GhostCat