Wednesday, June 25, 2014

Spring autowired testing problems


Lately I’ve been working on putting tests around some of our legacy (ie, untested) code.  Much of the app uses Spring’s auto-wiring, so we have fields that look like:

private @Autowired FieldValidator fieldValidator;

Spring can wire this together reflectively so there is no need to write a constructor or setters.


Therein lies my problem.


Without a constructor or setter, how can I (easily) write tests that inject mocks?  As far as I can tell, the official solution is to use Spring’s ReflectionTestUtils class that has a method setField to set these private instance fields.  I have two issues with this though.

First, it is very reflection’y, which is to say, not a natural code style.  Instead of action.setFieldValidator(mockValidator) that looks and behaviors like traditional Java, we are stuck with ReflectionTestUtils.setField(action, “fieldValidator”, mockValidator).  Yes, I can read it and know that it’s doing the same thing, but it’s not nearly as intuitive.  Even statically importing ReflectionTestUtils still leaves me with a setField method that requires a target instance and method passed in instead of, you know, passing something directly to the instance.  There’s a bit of a feeling of IoC here, which makes sense coming from Spring; but for a unit test, I WANT the control.

Second, and more importantly, it isn’t safe.  We have powerful IDEs to do everything except make toast (although, I wouldn’t be surprised if there’s an emacs command for that too), yet formidable Spring’s best advice here doesn’t make a peep at compile-time when there’s a flagrant error?  Try to pass in a value that isn’t compatible with the field?  Find out at runtime.  Typo the field name?  Find out at runtime.  Change the target and thus break the rest of the setter?  Find out at runtime.

No IDE will update this line when you refactor your code.  You should be able to change a private field safely basically at no expense, but now you’ll break your test – and won’t know it until it runs.  I understand that a test can and should be tightly coupled the class under test, but if they’re going to be coupled, can’t we at least keep them automatically in sync?


So if you use Spring’s autoMagic Autowired without a constructor or setter, how do you mock your tests?   

I’d love to know.

2 comments:

  1. I change the field's visibility from `private` to "default", i.e. package-protected.

    That lets you substitute mocks in your tests, and it's a little safer than private field + public getters/setters.

    ReplyDelete
    Replies
    1. Yea, I know that works. I've just never liked having to change visibility just for testing.
      After reviewing Michael Feather's Working With Legacy Code again (open next to me), maybe if I view it as opening a seam (the seam itself is already there, just not accessible) it'll feel more palatable.

      Delete