简体   繁体   中英

Best technique/workaround for mocking assignment of private field?

TDD and Mockito testing newb here... I have recently discovered about injection of mocks of private fields and am trying to get my ideas straight.

It may well be that my thinking on this particular matter is all wrong, but it's just that I often find a situation where I want to test that calling a particular method has resulted in a private field being assigned: before the call it was null , and after the call it is set to an instance of whatever class.

A mocked private field is a subclass of the class in question by the time you start to run your test method, so obviously testing for null is meaningless/doesn't work. Equally, if your method results in an assignment of this field this means that the mock variable has been replaced by a genuine instance. Thus, even if methods are called on it it won't lead to any interaction with the mock variable. In any event, if the test is just to check on the creation and assignment there is no reason you would want to have any interaction with it in the test.

Obviously one answer is to create a public get() method or a public create() method in the app class. But this gives me a bad feeling as it is clearly just being done for testing purposes.

Incidentally there will be those, many no doubt much more experienced than me at TDD, who will say that I'm getting too fine-grained and shouldn't be testing something as specific as assignment of a field. But my understanding of the most extreme approach to TDD is "don't write a line of code without a test". I really am trying to stick with this at this stage as far as possible. With experience maybe I will have greater understanding of how and when to depart from this principle.

It's just that assignment of a field seems to me to be a "class event" (of the host class) as worthy as any other type of event, such as a call of a method on the field once an instance has been assigned to the field. Shouldn't we be able to test for it in some way...? What do the Mockito pros who accept that this may be a legitimate thing to test for do in these circumstances?

later...

The comment from Fabio highlights the tension which, as a newb, I find puzzling, between "never write a line without a test" and "test only public methods": the effect of this is inevitably to expose much more stuff than I want, purely for the sake of testing.

And what he says about "changing tests all the time" I also find problematic: if we inject mocked private fields, and use them, then we are going to have to change the tests if we change the "implementation details". Nor does this idea fill me with dread: a need for change is perceived, you look at the very explanatory names of your existing test methods and decide which ones no longer apply, and of course you create new ones... the testing code, like the app code, will have to change all the time, right?

Isn't the testing code like some sort of die or mould, which guides and constrains the app code but at the same time can be detached from it? By Mockito allowing us to mock private fields a privileged form of access is already granted... who's to say how far this privileged access should rightly extend?

even later...

Jeff Bowman suggests this is a dup with this . I'm not entirely convinced, since I am asking a very specific thing for which it's just possibly there may be a specific answer (some Mockito means of detecting assignment). But his answer there seems very comprehensive and I'm trying to fully understand it all... including the whole dependency injection thing... and the compromises you seem have to make in "distorting" your public interface to make an app class as testable as you want to make it.

Simple example: you need to write a class Class1 with two methods Method1 and Method2 which return values provided by some Helper class.

  1. [Test] Start writing test for first method - it will not compile
  2. [Production] Create class Class1 and Method1 - compile ok and test fail
  3. [Production] In Method1 you create new instance of Helper and call some of this methods which return expected value - test pass
  4. [Test] Create another test case for same Method1 - test fail
  5. [Production] Make changes in Method1 to satisfy both test cases - tests are green
  6. [Production] Make some refactorings in Method1 if you see possibility - test must stay green
  7. [Test] Create test for Method2 which will update some public field Field1 of Class1 - will not compile
  8. [Production] Create method Method2 and field Field1 in Class1 - compile - ok, test fail
  9. [Production] Create new instance of Helper class in Method2 , call it method and update Field1 with expected value - test pass
  10. [Test] Create another test case for Method2 - test fail
  11. [Production] Make changes in Method2 to satisfy both test cases - tests are green

Now you have implemented needed behavior and noticed that both Method1 and Method2 create instance of Helper class.

  1. [Production] So you decide to declare private field of type Helper and use it in both methods - all tests are failed - because your private field not instantiated.
  2. [Production] - you instantiate new instance in the constructor - all tests are green

Later if you decide to move creation of Helper class outside and pass it to the Class1 as constructor's parameter, you just change your production code and tests will show you does you break anything without changing tests.

Main point of my answer, that if you think like "I want write this line of code - How I can test it" - it will goes you in thats kind of situation you got right now.
Try to first think what you want to test and after writing the test think how you will implement it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM