Re: [mockito] Odd Injection Behaviour

125 views
Skip to first unread message

Tomek Kaczanowski

unread,
Jan 10, 2013, 7:04:55 AM1/10/13
to moc...@googlegroups.com
this line got my attention

@InjectMocks private ClassToBeInjected injectedObj =
mock(ClassToBeInjected.class);

shouldn't you rather use this annotation on your SUT so it get the
mocks injected?
http://docs.mockito.googlecode.com/hg/org/mockito/InjectMocks.html

--
Regards / Pozdrawiam
Tomek Kaczanowski
http://practicalunittesting.com



2013/1/10 Jumwah <james.mil...@gmail.com>:
> Hello,
>
> I am getting some odd injection behavior when I was using Mockito 1.8.5, and
> when I recently upgraded to 1.9.5 it got worse and was causing a test to
> fail. I don't have much time to create a example right now, so I'll try to
> explain to see if anyone can see a rookie mistake.
>
> I was using injection like so:
>
> @Mock(name = "mousePosition") private Point2D mousePosition;
>
> @InjectMocks private ClassToBeInjected injectedObj =
> mock(ClassToBeInjected.class);
>
> Pretty straightforward. The ClassToBeInjected has several base classes and
> the field "mousePosition" belongs to one of the base classes. The class
> that owns it also has a field called "menuPosition" (also of Point2D type),
> and an even more base class has fields called "leftDragStart" and
> "leftDragStop" (also of Point2D type). All these fields are protected - if
> that makes a difference.
>
> In 1.8.5, the mock for mousePosition would be assigned to ALL of these
> fields! When I upgraded to 1.9.5, the mock for mousePosition was being
> assigned to menuPosition and leftDragStart. This caused my test to fail as
> the actual field in the ClassToBeInjected was left as null.
>
> In order to fix this, I had to add:
>
> @Mock(name = "menuPosition") private Point2D menuPosition;
>
> which interestingly enough, also stopped the mousePosition mock being
> assigned to the leftDragStart field! The most crazy part? A field called
> mouseListener (owned by Component which is a base class of
> ClassToBeInjected) is being assigned the ClassToBeInjected mock itself!! So
> it was injecting itself with... itself.
>
> This behaviour has been confirmed on two colleagues machines, so I don't
> have a build problem. This feels utterly bonkers to me - can anyone see why
> I would be getting this behviour?
>
> Thanks!
>
> --
> You received this message because you are subscribed to the Google Groups
> "mockito" group.
> To view this discussion on the web visit
> https://groups.google.com/d/msg/mockito/-/g27o4NIyoJ4J.
> To post to this group, send email to moc...@googlegroups.com.
> To unsubscribe from this group, send email to
> mockito+u...@googlegroups.com.
> For more options, visit this group at
> http://groups.google.com/group/mockito?hl=en.

Jumwah

unread,
Jan 10, 2013, 8:28:22 AM1/10/13
to moc...@googlegroups.com
Sorry, perhaps my name choice was poor, to clarify, mousePosition is meant to be injected into ClassToBeInjected.  Perhaps InjectionTarget (or just SUT) would have been a better choice.

ClassToBeInjected is the SUT.

Thanks!

Eric Lefevre-Ardant

unread,
Jan 10, 2013, 11:12:20 AM1/10/13
to moc...@googlegroups.com
If ClassToBeInjected is the SUT, then you should absolutely not mock it! (which is what you have done my calling mock(ClassToBeInjected.class).
Your code should instead look like this:
    @InjectMocks private  ClassToBeInjected injectedObj = new ClassToBeInjected();

PS : I know I'm starting to sound like an old saw, but my recommendation is always to avoid fancy annotations such as @Mock and @InjectMocks. Their behavior is not obvious enough (especially to newcomers) to make the code clearer, and they do not safe much actual code. Besides, being force to explicitely pass mocked dependencies make the pain more obvious, which is a good thing.




To view this discussion on the web visit https://groups.google.com/d/msg/mockito/-/m3HGdtuUqqUJ.

Jumwah

unread,
Jan 10, 2013, 11:23:50 AM1/10/13
to moc...@googlegroups.com
Thanks for your comments, but I do not have a choice.  This is legacy code (written by non-developers) and constructing the object will display a dialog on screen as well as interact with hundreds of singletons and maybe even the file system.  Refactoring this out is also not feasible at this time due to a wealth of other dependencies.  The test is attempting to verify that some small functionality of this class is working.  We currently have about 3% code coverage from our unit tests, I am trying to turn this around but... baby steps.  Thus, I use mocking extremely heavily at the moment.

Thanks!

Eric Lefevre-Ardant

unread,
Jan 10, 2013, 11:41:08 AM1/10/13
to moc...@googlegroups.com
Jumwah,

This does not quite make sense to me. The mock() method creates an instance of your class controlled my Mockito. None of its methods are available anymore, except in mocked form (except for final or static methods). Hence, I don't see how you can test anything at all!

Maybe there are bits that still behave in a way that suits you; it's hard to tell without a more complete example. But it seems clear to me that you are trying to do things that Mockito does not support.
In particular, I am not particularly surprised that injecting mocked dependencies to a mocked object does not work. That is not what mocked objects do!

To sum up: I do not believe that you are actually going to make this work. Your best option is probably still to find a way to instanciate your class under test. Yes, you made it clear that it's painful, maybe even impossible. That said, I've heard that mocked Swing contexts exist.
Your second best option is to extract the code you want to test to another class.

Another advice, though: you owe it to yourself to read Working Effectively with Legacy Code.

Eric


To view this discussion on the web visit https://groups.google.com/d/msg/mockito/-/OykoQ_zes5AJ.

Jumwah

unread,
Jan 10, 2013, 11:55:13 AM1/10/13
to moc...@googlegroups.com
Hi Eric,

They're available by using thenCallRealMethod()/doCallRealMethod() on the public/protected ones, also the private methods are not stubbed and so function normally, assuming any fields initialized in the constructor they use are initialized some other way - and I am achieving this by injection.

In general, the injection does work - although it has some peculiarities as I've described.  Ultimately, I have managed to fix the test - by adding the line extra line as described.  However, I am curious as to why this problem occurs.  If you are saying that there is something deep inside Mockito that means this just shouldn't work (although I am not sure why - it doesn't say you can't do this) then that's a different matter - but for the most part, it does work.

Although I've not read Working Effectively with Legacy Code cover to cover, I have it on my desk and reference it regularly - as well as bunch of other clean code books.  Unfortunately, they've never come across the code base I just have ;)

Thanks!

Brice Dutheil

unread,
Jan 10, 2013, 12:50:46 PM1/10/13
to moc...@googlegroups.com
Hi,

Actually I support the point of Eric, about using injection in a mock, that is definitely a test code that is waiting to break. Mocks skip the constructor as you may have already noticed.

Anyway what you want to achieve is a partial mock (which is usually recognized as a test smell). In this case, you should use the spy semantics (spies are a specific kind of mocks), you can create one using the standard API, or the annotation, for example :

spy(new ClassToBeInjected());
@Spy private ClassToBeInjected classToBeInjected = new ClassToBeInjected();


Note that since 1.9.5, if you are using annotations, Mockito can try to find the best matching constructor, see the javadoc for that matter.

Now, about injection, what was happening in 1.8.5, was that Mockito was way too "aggressive" when injecting stuff, this caused some havoc in other project. Remember that injection stuff is just a simple injection utility, not a fully fledged injection framework and that most of injection is discovered almost automatically which means it has shortcomings that the ode cannot overcome uniquely on the Mockito side.



Note that since 1.9.5, mocks created by Mockito.mock() will be included in the list of injection candidate. That does explain that the mock of ClassToInjected is injected in itself somewhere in an upper class.


So the combination of the these new features and the way your legacy is made, makes some funky behavior. In my opinion you should first refactor your legacy, typically extracting the relevant part as Eric suggested would be relevant here. Then you could live with Mockito happily.

Hope that helps

-- Brice Dutheil

To view this discussion on the web visit https://groups.google.com/d/msg/mockito/-/tLYo2zTWLb8J.

Jumwah

unread,
Jan 11, 2013, 7:45:44 AM1/11/13
to moc...@googlegroups.com
 Hi Brice,

Thanks for you explanation about why this is happening.

It is not that I disagree with Eric, it's simply that I am in the unenviable position of being fairly new in job - and also the main testing driver.  I don't have much support with regards to doing necessary work to start getting foothold.  Essentially, unless heavy refactoring is needed for production code I wont get it past a code review "just for a test". 

So, unfortunately I cannot use Spy in this context as it is exactly the constructors I wish to stub - hence the mocking.  The main problem being many of the base classes do a great deal of work, have huge external dependencies and take concrete classes as parameters.  Interfaces are fairly uncommon here, and useful interfaces are even more so.  The failing test is one that used to work, it was only during the upgrade that I found these problems, so I wouldn't stand a chance of getting the time to perform the required work in order to stop using the injection.  For now, I am just over the moon that something is being tested.  Anything is better than nothing, right?

Thanks both of you for your help and suggestions though! :)

Brice Dutheil

unread,
Jan 11, 2013, 8:22:45 AM1/11/13
to moc...@googlegroups.com
mmmmh, it could be interesting to have a mockNames field in @InjectMocks, for such case were automatic injection is problematic.

In the mean time, if you want to benefit the feature from Mockito 1.9.5 in other area, you'll have to write your own injection tool for that test, it's not that difficult, especially you know which mock you want to inject in fields.

Cheers,

-- Brice


To view this discussion on the web visit https://groups.google.com/d/msg/mockito/-/YQK_kfMxJn4J.
Reply all
Reply to author
Forward
0 new messages