Skip to content

Bean created by @MockBean still has its fields autowired #6663

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
WildDev opened this issue Aug 16, 2016 · 16 comments
Closed

Bean created by @MockBean still has its fields autowired #6663

WildDev opened this issue Aug 16, 2016 · 16 comments
Assignees
Labels
type: bug A general bug
Milestone

Comments

@WildDev
Copy link

WildDev commented Aug 16, 2016

The following code taken from the doc doesn't work as expected:

@RunWith(SpringRunner.class)
@WebMvcTest(TestController.class)
public class TestControllerTest {

    @MockBean
    private TestValidator testValidator;

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    public void test() throws Exception {
        // no-opts
    });
}

... where's the mocked bean is:

@Component
public class TestValidator implements Validator {

    @Autowired
    private TestRepository testRepository;
    ...
}

during test startup it fails:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'testController': Unsatisfied dependency expressed through field 'testValidator': Error creating bean with name 'com.example.TestValidator#0': Unsatisfied dependency expressed through field 'categoryRepository': No qualifying bean of type [com.example.TestRepository] found for dependency [com.example.TestRepository]: expected at least 1 bean which qualifies as autowire candidate for this dependency. 

Java: 1.8.0_65
Spring Boot: 1.4.0.RELEASE & 1.4.1-BUILD-SNAPSHOT
Maven: 3.3.3

Test project on GitHub

@wilkinsona
Copy link
Member

wilkinsona commented Aug 16, 2016

Thanks for the sample.

I don't think this has anything to do with @MockBean. The problem is that you're using @WebMvcTest but with a bean that autowires a Data JPA repository. Data JPA repositories aren't created when using @WebMvcTest.

Rather than mocking TestValidator, is there any reason why you can't mock Validator instead? You'd have to change the injection point in TestController. Alternatively, you can also avoid the problem by using constructor injection instead of field injection.

I guess we could look at disabling autowiring for beans created using @MockBean so that if it's a mock created from a class (rather than from an interface) no attempt is made to inject anything into it.

@wilkinsona wilkinsona changed the title @MockBean doesn't work with the nested dependencies Bean created by @MockBean still has its fields autowired Aug 16, 2016
@WildDev
Copy link
Author

WildDev commented Aug 16, 2016

@wilkinsona ,

I don't think this has anything to do with @MockBean. The problem is that you're using @WebMvcTest but with a bean that autowires a Data JPA repository. Data JPA repositories aren't created when using `@WebMvcTest.

yes, but there's an any controller is interactes with the database throught service layer or whatever else. So, to test a controller it's neccessary to mock it's dependencies anyway. Data JPA repository shouldn't to be autowired at all since it's just a dependency of the dummy object.

Will try your suggestion but IMAO, the @MockBean annotation should to behave exactly like Mockito's @Mock does - it just makes a dummy object without any nested dependencies resolution that easily injectable to the covered by test class.

@wilkinsona
Copy link
Member

wilkinsona commented Aug 16, 2016

Will try your suggestion but IMAO, the @MockBean annotation should to behave exactly like Mockito's @Mock does

In terms of how the mock is created, that's exactly what @MockBean does. However, we have to do more than @Mock – the mock has to be added to the application context so that it can be injected into other components. It's the fact that's it's a bean in the application context that means that an attempt is made to inject its fields which is what's causing the problem you've reported.

@WildDev
Copy link
Author

WildDev commented Aug 16, 2016

@wilkinsona , then it behaves rather like a stub that not always sufficient for the testing :(

@snicoll
Copy link
Member

snicoll commented Aug 17, 2016

Looks like a rephrased clone of #6658

@WildDev
Copy link
Author

WildDev commented Aug 17, 2016

@snicoll , has nothing with the #6658 at all

@snicoll
Copy link
Member

snicoll commented Aug 17, 2016

Yeah, @wilkinsona just told me. The complaints look alike :)

wilkinsona added a commit to wilkinsona/spring-boot that referenced this issue Aug 17, 2016
Post-processing of mocked beans causes a number of problems:

 - The mock may be proxied for asynchronous processing which can cause
   problems when configuring expectations on a mock (spring-projectsgh-6573)
 - The mock may be proxied so that its return values can be cached or
   so that its methods can be transactional. This causes problems with
   verification of the expected calls to a mock (spring-projectsgh-6573, spring-projectsgh-5837)
 - If the mock is created from a class that uses field injection, the
   container will attempt to inject values into its fields. This causes
   problems if the mock is being created to avoid the use of one of
   those dependencies (spring-projectsgh-6663)
 - Proxying a mocked bean can lead to a JDK proxy being created
   (if proxyTargetClass=false) as the mock implements a Mockito
   interface. This can then cause injection failures as the types don’t
   match (spring-projectsgh-6405, spring-projectsgh-6665)

All of these problems can be avoided if a mocked bean is not
post-processed. Avoiding post-processing prevents proxies from being
created and autowiring from being performed. This commit avoids
post-processing by registering mocked beans as singletons rather than
via a bean definition.
@wilkinsona wilkinsona added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged labels Aug 18, 2016
@wilkinsona wilkinsona added this to the 1.4.1 milestone Aug 18, 2016
@SingleShot
Copy link

SingleShot commented Aug 30, 2016

I experience this same problem when the unit under test's dependencies are plain old Spring beans that themselves have autowired dependencies. If the unit's dependencies are interfaces, no problem, they mock fine. But if classes, the mocking does not work completely and requires the dependencies of the dependency to be wired in.

The "workaround" for this is to provide @MockBeans for dependencies of the dependency, and dependencies of the dependency's dependencies and so on down, which is pretty ugly. Another is to only use interfaces for your dependencies - which I prefer - but you don't always have that luxury when the dependency is not in your control. Of course, I could wrap it in my own interface but that's extra code that I wouldn't need if @MockBean behaved like Mockito class mocking from the standpoint of not needing to provide dependencies to the mock.

For me - someone trying to port a legacy (and very messy) Spring app over to several Spring Boot apps - getting this fixed is important. (Oh I see it is no longer waiting for triage - thanks!)

snicoll pushed a commit that referenced this issue Sep 2, 2016
Post-processing of mocked beans causes a number of problems:

 - The mock may be proxied for asynchronous processing which can cause
   problems when configuring expectations on a mock (gh-6573)
 - The mock may be proxied so that its return values can be cached or
   so that its methods can be transactional. This causes problems with
   verification of the expected calls to a mock (gh-6573, gh-5837)
 - If the mock is created from a class that uses field injection, the
   container will attempt to inject values into its fields. This causes
   problems if the mock is being created to avoid the use of one of
   those dependencies (gh-6663)
 - Proxying a mocked bean can lead to a JDK proxy being created
   (if proxyTargetClass=false) as the mock implements a Mockito
   interface. This can then cause injection failures as the types don’t
   match (gh-6405, gh-6665)

All of these problems can be avoided if a mocked bean is not
post-processed. Avoiding post-processing prevents proxies from being
created and autowiring from being performed. This commit avoids
post-processing by registering mocked beans as singletons as well as
via a bean definition. The latter is still used by the context for type
matching purposes.

Closes gh-6573, gh-6663, gh-6664
@snicoll
Copy link
Member

snicoll commented Sep 2, 2016

Fixed in 0e00a49

@snicoll snicoll closed this as completed Sep 2, 2016
@StasKolodyuk
Copy link

StasKolodyuk commented Oct 18, 2016

I experience the same issue for @SpyBean. However, the @MockBean works correctly in 1.4.1.RELEASE

@wilkinsona
Copy link
Member

@StasKolodyuk That's to be expected. Unlike a mock, a spy is a wrapper around a "proper" bean. For the bean that's being spied upon to work properly it needs to have its dependencies injected.

@StasKolodyuk
Copy link

@wilkinsona That makes sense, thank you!

@danilobalarini
Copy link

danilobalarini commented Apr 3, 2017

Just to say that i got rid of this bug by removing the @ComponentScan from my @SpringBootApplication -
Application.class

There is no need for it anymore (using spring-boot 1.5.2)

@stahloss

This comment has been minimized.

@snicoll

This comment has been minimized.

@stahloss

This comment has been minimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug A general bug
Projects
None yet
Development

No branches or pull requests

8 participants