Skip to content

@MockBean does not handle parameterized types #6602

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
rocketraman opened this issue Aug 10, 2016 · 7 comments
Closed

@MockBean does not handle parameterized types #6602

rocketraman opened this issue Aug 10, 2016 · 7 comments
Assignees
Labels
type: bug A general bug
Milestone

Comments

@rocketraman
Copy link

rocketraman commented Aug 10, 2016

Spring Boot 1.4.0.RELEASE

I am trying to use the new @MockBean annotation to inject two mocks of the same parameterized interface, each with a different type i.e.:

  @MockBean
  private IdentityProvider<PasswordIdentity> passwordIdentityProvider;

  @MockBean
  private IdentityProvider<Oauth2Identity> oauth2IdentityProvider;

When running this test with @SpringBootTest I get the following error:

java.lang.IllegalStateException: Duplicate mock definition [MockDefinition@1b68b9a4 name = '', classToMock = IdentityProvider, extraInterfaces = set[[empty]], answer = RETURNS_DEFAULTS, serializable = false, reset = AFTER]

    at org.springframework.util.Assert.state(Assert.java:392)
    at org.springframework.boot.test.mock.mockito.DefinitionsParser.addDefinition(DefinitionsParser.java:119)
    at org.springframework.boot.test.mock.mockito.DefinitionsParser.parseMockBeanAnnotation(DefinitionsParser.java:97)
    at org.springframework.boot.test.mock.mockito.DefinitionsParser.parseElement(DefinitionsParser.java:76)
    at org.springframework.boot.test.mock.mockito.DefinitionsParser.access$000(DefinitionsParser.java:42)
    at org.springframework.boot.test.mock.mockito.DefinitionsParser$1.doWith(DefinitionsParser.java:67)
    at org.springframework.util.ReflectionUtils.doWithFields(ReflectionUtils.java:692)
    at org.springframework.util.ReflectionUtils.doWithFields(ReflectionUtils.java:672)
    at org.springframework.boot.test.mock.mockito.DefinitionsParser.parse(DefinitionsParser.java:62)
    at org.springframework.boot.test.mock.mockito.MockitoContextCustomizerFactory.createContextCustomizer(MockitoContextCustomizerFactory.java:38)
    at org.springframework.test.context.support.AbstractTestContextBootstrapper.getContextCustomizers(AbstractTestContextBootstrapper.java:418)
    at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildMergedContextConfiguration(AbstractTestContextBootstrapper.java:387)
    at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildDefaultMergedContextConfiguration(AbstractTestContextBootstrapper.java:323)
    at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildMergedContextConfiguration(AbstractTestContextBootstrapper.java:277)
    at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildTestContext(AbstractTestContextBootstrapper.java:112)
    at org.springframework.boot.test.context.SpringBootTestContextBootstrapper.buildTestContext(SpringBootTestContextBootstrapper.java:74)
    at org.springframework.test.context.TestContextManager.<init>(TestContextManager.java:120)
    at org.springframework.test.context.TestContextManager.<init>(TestContextManager.java:105)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTestContextManager(SpringJUnit4ClassRunner.java:152)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.<init>(SpringJUnit4ClassRunner.java:143)
    at org.springframework.test.context.junit4.SpringRunner.<init>(SpringRunner.java:49)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at org.junit.internal.builders.AnnotatedBuilder.buildRunner(AnnotatedBuilder.java:104)
    at org.junit.internal.builders.AnnotatedBuilder.runnerForClass(AnnotatedBuilder.java:86)
    at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
    at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:26)
    at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
    at org.junit.internal.requests.ClassRequest.getRunner(ClassRequest.java:33)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:96)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:253)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:84)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

Note that exposing two beans of these parameterized types works fine in the regular non-test Spring context i.e. the types are injected correctly by Spring.

In addition, I can create the mocks manually via Mockito, also correctly.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Aug 10, 2016
@philwebb
Copy link
Member

I had a quick look but I think we might need some updates to Spring's RootBeanDefinition in order to support this.

Currently I don't think we have a way to programmatically register a bean definition with a specific ResolvableType. What we effectively need is a RootBeanDefinition.setTargetResolvableType(...) method and an update to GenericTypeAwareAutowireCandidateResolver that uses it.

@jhoeller am I correct with this assumption? Would adding support be something we'd consider in a point release?

@philwebb philwebb added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged labels Aug 10, 2016
@philwebb
Copy link
Member

@rocketraman You may be able to work around this issue in the meantime by specifying the specific bean names to replace when you declare your mocks. Something like:

  @MockBean(name = "passwordIdentityProvider")
  private IdentityProvider<PasswordIdentity> passwordIdentityProvider;

  @MockBean(name = "oauth2IdentityProvider")
  private IdentityProvider<Oauth2Identity> oauth2IdentityProvider;

@rocketraman
Copy link
Author

rocketraman commented Aug 10, 2016

@philwebb Yes, I can confirm that workaround works (though I need to explicitly specify the value in the @Component annotation). Or one can just define the mocks as done pre-1.4 in a test configuration.

@philwebb
Copy link
Member

@rocketraman Thanks for raising the issue. I'd like to provide better support for this but it may take some time since changes to Spring Framework are likely required.

@jhoeller
Copy link

We could indeed consider some extra field on a bean definition that indicates a generic target type to specifically match instead of getting that information from the bean class or factory method. Looks doable for 4.3.3 still, simply sitting next to our existing target type field on a root bean definition and not used unless specifically set on registration.

@philwebb
Copy link
Member

Related Spring Framework Issue : https://jira.spring.io/browse/SPR-14580

@philwebb
Copy link
Member

Fix is here https://github.com/philwebb/spring-boot/tree/gh-6602, just waiting for SPR-14580 to get merged.

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

5 participants