Skip to content
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

Test Bean Overrides honor fallback qualifier instead of @Primary semantics #34374

Closed
patdlanger opened this issue Feb 6, 2025 · 7 comments
Closed
Assignees
Labels
in: test Issues in the test module type: bug A general bug
Milestone

Comments

@patdlanger
Copy link

patdlanger commented Feb 6, 2025

Using Spring Boot 3.4.2

Bug Reports
Given the following classes

@Component
public class BaseService {

    public void doSomething() {}
}
@Primary
@Component
public class ServiceB extends BaseService {
}
@Component
public class ServiceA {

    private final BaseService serviceB;

    public ServiceA(BaseService serviceB) {
        this.serviceB = serviceB;
    }

    public void callB() {
        serviceB.doSomething();
    }
}

The following test works with @MockBean but fails with @MockitoBean because the mocked bean is not injected into ServiceA

@SpringBootTest
class MockitobeanApplicationTests {

    @Autowired
    ServiceA serviceA;

    @MockitoBean
    BaseService baseService;

    @Test
    void contextLoads() {
        serviceA.callB();

        verify(baseService).doSomething();
    }

}

Enhancements requests
This used to/does work with @MockBean

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Feb 6, 2025
@patdlanger patdlanger changed the title @MockitoBean behaviour with subtyped class annotated @Primary @MockitoBean behaviour with subclassed class annotated @Primary Feb 6, 2025
@sbrannen sbrannen added the in: test Issues in the test module label Feb 6, 2025
@sbrannen sbrannen self-assigned this Feb 6, 2025
@sbrannen
Copy link
Member

sbrannen commented Feb 6, 2025

Hi @patdlanger,

Congratulations on submitting your first issue for the Spring Framework! 👍

I tried to reproduce the behavior you're reporting with the following.

@SpringJUnitConfig
public class MockPrimaryBeanTests {

	@Autowired
	ServiceA serviceA;

	@MockitoBean
	BaseService baseService;


	@Test
	void contextLoads() {
		serviceA.callB();

		verify(baseService).doSomething();
	}


	@Configuration
	@Import({ BaseService.class, ServiceA.class, ServiceB.class })
	static class Config {
	}

	@Component
	public static class BaseService {

		public void doSomething() {
		}
	}

	@Primary
	@Component
	public static class ServiceB extends BaseService {
	}

	@Component
	public static class ServiceA {

		private final BaseService serviceB;


		public ServiceA(BaseService serviceB) {
			this.serviceB = serviceB;
		}

		public void callB() {
			this.serviceB.doSomething();
		}
	}

}

But that passes for me with @MockBean and @MockitoBean (against the 6.2.x branch).

With which version of spring-test did you encounter the failure?

@sbrannen sbrannen added the status: waiting-for-feedback We need additional information before we can continue label Feb 6, 2025
@sbrannen
Copy link
Member

sbrannen commented Feb 6, 2025

@patdlanger
Copy link
Author

Hi @sbrannen ,
you are right. With your test setup both @MockitoBean and @MockBean work. It seems that the issue lies in @SpringBootTest . I pushed a small repo here https://github.com/patdlanger/mockitobeanspring/blob/main/src/test/java/com/mockitobean/MockitobeanSpringBootTest.java
The test which uses @SpringBootTest fails

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Feb 7, 2025
@sbrannen
Copy link
Member

sbrannen commented Feb 7, 2025

Thanks for the feedback and the reproducer, @patdlanger. 👍

It seems that the issue lies in @SpringBootTest.

On the surface, yes, that appears to be the case.

However, after debugging it, I figured out that the core difference results from the use of @ComponentScan for @SpringBootTest vs. the use of @Import in the example I created.

With component scanning the bean names are serviceA, serviceB, and baseService. Whereas, with @Import the bean names are the fully-qualified class names for the components.

With @MockBean, the field name is not used as a fallback qualifier, and @Primary is always honored. However, with @MockitoBean, the field name is used as a fallback qualifier which is applied before honoring @Primary.

The latter is what causes this bug.

Specifically, when the name of the BaseService bean is baseService, @MockitoBean ends up mocking the BaseService bean instead of the ServiceB bean, even though the ServiceB bean is the @Primary bean of type BaseService.

In the following modified version of my example, each @Component declares an explicit bean name which matches the bean name generated during component scanning.

The test class fails "as is", but if you change the name of the field to something other than baseService (for example, baseServiceX) the test then passes. Again, the reason is that the name of the field will no longer match eagerly as the fallback qualifier before applying @Primary semantics.

@SpringJUnitConfig
class MockPrimaryBeanTests {

	@Autowired
	ServiceA serviceA;

	@MockitoBean
	BaseService baseService;

	@Test
	void contextLoads() {
		serviceA.callB();

		verify(baseService).doSomething();
	}

	@Configuration
	@Import({ BaseService.class, ServiceA.class, ServiceB.class })
	static class Config {
	}

	@Component("baseService")
	static class BaseService {
		public void doSomething() {
		}
	}

	@Primary
	@Component("serviceB")
	static class ServiceB extends BaseService {
	}

	@Component("serviceA")
	static class ServiceA {

		private final BaseService serviceB;

		public ServiceA(BaseService serviceB) {
			this.serviceB = serviceB;
		}

		public void callB() {
			this.serviceB.doSomething();
		}
	}
}

@sbrannen sbrannen added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged or decided on status: feedback-provided Feedback has been provided labels Feb 7, 2025
@sbrannen sbrannen added this to the 6.2.3 milestone Feb 7, 2025
@sbrannen sbrannen changed the title @MockitoBean behaviour with subclassed class annotated @Primary Test Bean Overrides honor fallback qualifier instead of @Primary semantics Feb 7, 2025
@sbrannen
Copy link
Member

sbrannen commented Feb 7, 2025

I just pushed a fix for this which will be available in the upcoming 6.2.3 release.

However, @patdlanger, it would be great if you could try out a 6.2.3 snapshot before the release to confirm that everything is good on your end.

Cheers!

@patdlanger
Copy link
Author

Thanks @sbrannen the snapshot fixes our issue.

@sbrannen
Copy link
Member

Thanks @sbrannen the snapshot fixes our issue.

Great! Thanks for letting us know.

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

No branches or pull requests

3 participants