Giter Club home page Giter Club logo

Comments (12)

manovotn avatar manovotn commented on August 15, 2024 1

CC @manovotn

I think a sensible approach is to:

  • Fix the inconsistency we have in that we should also take into consideration method params with @InjectMock when marking beans as unremovable
  • Add documentation that will mention that @InjectMock isn't a universal replacement for Mockito. It has a narrow use case for mocking missing beans.

The way JUnit works, you should be able to meld together several extensions and Mockito has their own one that you can leverage for what you are trying to do. What @mkouba suggests in the last comment is IMO the "cleanest" approach you can get.

from quarkus.

quarkus-bot avatar quarkus-bot commented on August 15, 2024

/cc @Ladicek (arc), @manovotn (arc), @mkouba (arc)

from quarkus.

mkouba avatar mkouba commented on August 15, 2024

As far as i understand QuarkusComponentTest is just 'mockito-test' with other annotations.

@octopus-prime It is not.

So QuarkusComponentTest still relies on Mockito.

Not really, you can use QuarkusComponentTest even without Mockito and mocks in general.

The difference is that QuarkusComponentTest starts a real CDI container plus configuration service. Mocks are automatically used if a component has an unsatisfied dependency; e.g. there's a component Foo which also depends on Bar. If you test Foo in your QuarkusComponentTest but no Bar bean is available then a mock is injected instead.

Expected behavior

A fully initialized mockito - with parameter resolvers.

We don't use the MockitoExtension at all. Therefore, the @Mock parameters may not be resolved.

If you need to inject an unconfigured mock in a test method argument then replace the @Mock with @InjectMock.

from quarkus.

octopus-prime avatar octopus-prime commented on August 15, 2024
@QuarkusComponentTest
class ReadFallControllerTest {

    @Inject
    ReadFallController readFallController;

    @InjectMock
    FallRepository fallRepository;

    @InjectMock
    FallMapper fallMapper;

    @Test
    void readFall(@InjectMock Fall fall, @InjectMock FallResponse fallResponse) {
        when(fallRepository.findByFallNummerOrThrow(TestData.FALLNUMMER)).thenReturn(fall);
        when(fallMapper.map(fall)).thenReturn(fallResponse);

        FallResponse result = readFallController.readFall(TestData.FALLNUMMER);

        assertThat(result, is(fallResponse));
    }
}

->

Failed to resolve parameter
No matching bean found for the type

from quarkus.

mkouba avatar mkouba commented on August 15, 2024
Failed to resolve parameter
No matching bean found for the type

Could you share the full stacktrace pls?

Also it would be great if you could share a small project that would contain the "full" setup of your test.

from quarkus.

octopus-prime avatar octopus-prime commented on August 15, 2024

https://github.com/octopus-prime/qct

The 'BrokenFooControllerTest' will produce

Failed to resolve parameter [com.example.Foo arg0] in method [void com.example.BrokenFooControllerTest.getFoo(com.example.Foo,com.example.FooResponse)]: No matching bean found for the type [class com.example.Foo] and qualifiers []
org.junit.jupiter.api.extension.ParameterResolutionException: Failed to resolve parameter [com.example.Foo arg0] in method [void com.example.BrokenFooControllerTest.getFoo(com.example.Foo,com.example.FooResponse)]: No matching bean found for the type [class com.example.Foo] and qualifiers []
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
Caused by: java.lang.IllegalStateException: No matching bean found for the type [class com.example.Foo] and qualifiers []
	at io.quarkus.test.component.QuarkusComponentTestExtension.supportsParameter(QuarkusComponentTestExtension.java:300)
	at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:178)
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
	at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179)
	at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1708)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)

from quarkus.

mkouba avatar mkouba commented on August 15, 2024

https://github.com/octopus-prime/qct

The 'BrokenFooControllerTest' will produce

Failed to resolve parameter [com.example.Foo arg0] in method [void com.example.BrokenFooControllerTest.getFoo(com.example.Foo,com.example.FooResponse)]: No matching bean found for the type [class com.example.Foo] and qualifiers []
org.junit.jupiter.api.extension.ParameterResolutionException: Failed to resolve parameter [com.example.Foo arg0] in method [void com.example.BrokenFooControllerTest.getFoo(com.example.Foo,com.example.FooResponse)]: No matching bean found for the type [class com.example.Foo] and qualifiers []
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
Caused by: java.lang.IllegalStateException: No matching bean found for the type [class com.example.Foo] and qualifiers []
	at io.quarkus.test.component.QuarkusComponentTestExtension.supportsParameter(QuarkusComponentTestExtension.java:300)
	at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:178)
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
	at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179)
	at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1708)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)

👍 I'll take a look tomorrow.

from quarkus.

mkouba avatar mkouba commented on August 15, 2024

@octopus-prime Ok, so the problem is that @InjectMock is not intended for injection of a mock of any arbitrary object. It's meant to be used for beans; see the javadoc "Instructs the test engine to inject a mock instance of a bean into the annotated field or parameter.".

Now in the reproducer, Foo and FooResponse are not beans but more like DTOs. For these objects, @org.mockito.Mock or Mockito#mock(Class) are more appropriate, i.e. something like:

    @Test
    void getFoo() {
        Foo foo = Mockito.mock(Foo.class);
        FooResponse fooResponse = Mockito.mock(FooResponse.class);
        
        when(fooRepository.findFoo("1")).thenReturn(foo);
        when(fooMapper.mapFoo(foo)).thenReturn(fooResponse);

        FooResponse result = fooController.getFoo("1");

        assertEquals(result, fooResponse);
    }

Again, for QuarkusComponentTest it's not the goal to ease the creation of mocks but create mocked beans automatically if a component under the test (a CDI bean) has an unsatisfied dependency.

I wonder if we should change the behavior but I think that it does not make sense to create a mock bean for such an object. It seems like a misuse of the API. But I agree that we should at least improve the error message so that it's more clear. If we find a way to detect this kind of misuse...

CC @manovotn

from quarkus.

octopus-prime avatar octopus-prime commented on August 15, 2024

Strange... "@InjectMock is not intended for injection of a mock of any arbitrary object."... But

@QuarkusComponentTest
class UglyFooControllerTest {

    @Inject
    FooController fooController;

    @InjectMock
    FooRepository fooRepository;

    @InjectMock
    FooMapper fooMapper;

    @InjectMock
    Foo foo;

    @InjectMock
    FooResponse fooResponse;

    @Test
    void getFoo() {
        when(fooRepository.findFoo("1")).thenReturn(foo);
        when(fooMapper.mapFoo(foo)).thenReturn(fooResponse);

        FooResponse result = fooController.getFoo("1");

        assertEquals(fooResponse, result);
    }
}

Works fine.

However... both solutions need extra-lines of code :-(

from quarkus.

mkouba avatar mkouba commented on August 15, 2024

Works fine.

Indeed, there's an inconsistency because @InjectMock field does contribute to the logic that is used to exclude unused beans from removal and @InjectMock parameter does not (a new bean is created for each @InjectMock but then removed if not used in a tested component). We should unify this logic. But I'm not quite sure in which way. If we want to be more correct and resource efficient then @InjectMock field should not work either. And we could make the @InjectMock parameter work but it would still be a misuse and waste of resources in the sense that a new bean is created just to create an empty mock 🤷.

However... both solutions need extra-lines of code :-(

Yes, they need. There's one more alternative:

import org.mockito.Mock;
import io.quarkus.test.component.SkipInject;

@ExtendWith(MockitoExtension.class)
@SkipInject // needed to tell the QuarkusComponentTest to skip the @Mock params
@Test
void getFoo(@Mock Foo foo, @Mock FooResponse fooResponse) {
    when(fooRepository.findFoo("1")).thenReturn(foo);
    when(fooMapper.mapFoo(foo)).thenReturn(fooResponse);
    FooResponse result = fooController.getFoo("1");
    assertEquals(result, fooResponse);
}

from quarkus.

mkouba avatar mkouba commented on August 15, 2024

I think a sensible approach is to:

* Fix the inconsistency we have in that we should also take into consideration method params with `@InjectMock` when marking beans as unremovable

* Add documentation that will mention that `@InjectMock` isn't a universal replacement for Mockito. It has a narrow use case for mocking missing beans.

We will also skip param injection for params annotated with @org.mockito.Mock so that @SkipInject is not needed.

from quarkus.

mkouba avatar mkouba commented on August 15, 2024

@octopus-prime pull request sent! ;-)

from quarkus.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.