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

@SpringBean stubbed methods return null #1084

Closed
ahlinist opened this issue Jan 26, 2020 · 11 comments
Closed

@SpringBean stubbed methods return null #1084

ahlinist opened this issue Jan 26, 2020 · 11 comments

Comments

@ahlinist
Copy link

ahlinist commented Jan 26, 2020

Issue description

This is a duplication of my question on StackOverflow: https://stackoverflow.com/questions/59886870/spock-integration-tests-stubed-method-returns-null
When using @SpringBean stubbed method returns null:

@SpringBean
GpioController gpioController = Stub() {
    provisionDigitalInputPin(_, _, _) >> { throw new RuntimeException("I'm stubbed!") } //this doesn't work neither here nor in "given" section
}

I also tried stubbing in the "given" section:

@SpringBean
GpioController gpioController = Stub()

def "test context loads"() {
    given:
    gpioController.provisionDigitalInputPin(_, _, _) >> { throw new RuntimeException("I'm stubbed!") }
...

I've tried several approaches and none works. What am I missing to make @SpringBean work as given here?

A similar example with Junit4/Mockito works fine.

The code is available via URL: https://github.com/ahlinist/raspberry-pi4j/tree/feature/integration-tests

How to reproduce

I've created a separate source set so just run:
./gradlew integrationTest

Additional Environment information

gradle wrapper (6.0.1)
java 12.0.2
groovy 2.5
Intellij IDEA 2019.3
Ubuntu 16.04

Java/JDK

java version "12.0.2" 2019-07-16
Java(TM) SE Runtime Environment (build 12.0.2+10)
Java HotSpot(TM) 64-Bit Server VM (build 12.0.2+10, mixed mode, sharing)

Groovy version

Groovy Version: 2.5.8 JVM: 12.0.2 Vendor: Oracle Corporation OS: Linux

Build tool version

Gradle

wrapper of version 6.0.1

Operating System

Ubuntu 16.04

IDE

IntelliJ, 2019.3

Build-tool dependencies used

Gradle/Grails

testImplementation 'org.spockframework:spock-core:1.3-groovy-2.5'
testImplementation 'org.spockframework:spock-spring:1.3-groovy-2.5'
@kriegaex
Copy link
Contributor

kriegaex commented Jan 27, 2020

Before someone tries, the issue is the same with a manually configured detached mock.

@leonard84
Copy link
Member

The repository is quite complex, so I didn't have time to search through it. My initial guess is that you try to access the stub before it was attached to the Specification.
http://spockframework.org/spock/docs/1.3/all_in_one.html#_mocks

Although the mocks can be created outside of a specification, they only work properly inside the scope of a specification. All interactions with them until they are attached to one, are handled by the default behavior and not recorded.

@kriegaex
Copy link
Contributor

kriegaex commented Jan 28, 2020

@leonard84: That indeed is seems to be the case. Before the test can even start to run, while wiring the Spring beans when starting the Boot server, the mock is already referenced and injected. But how can the OP @ahlinist avoid this problem to happen? The @SpringBean definition definitely occurs inside the specification. The same applies to an alternative detached mock with @AutoAttach which I tried instead.

@leonard84
Copy link
Member

There is currently no way to define behavior outside the scope of the specification and I don't see a good way to add it. So the only Solution would be to refactor the Spring configuration.

We could add a flag to @SpringBean use the Stub behavior (org.spockframework.mock.EmptyOrDummyResponse) instead of returning null, when not attached to the specification, but I don't know if that will really help or might introduce more confusion for other users.

@kriegaex
Copy link
Contributor

kriegaex commented Feb 5, 2020

There is currently no way to define behavior outside the scope of the specification

Can you maybe explain in other words? It seems I am lacking some knowledge here. The mock and its behaviour are defined inside the specification, not outside. The Spring app is started from withing the specification too. Why is there no way to finish the mock/stub definition before firing up the Spring application? Shouldn't that always be taken care of as part of preparing the test to run?

I remember in the past I implemented my own IDefaultResponse always returning this in order to more easily test fluent APIs. Do you think that explicitly tailoring an implementation to behave like the OP wants to would be an option? But actually it sounds unnatural and contrived.

I do think the order of events in the bootstrapping process for these kinds of tests ought to be changed. I know it is easier said than done, maybe. But as it stands, Spock mocks with stubbed methods are pretty much useless for Spring tests in this kind of (not so unusual) situation. If this makes or breaks the decision for dev teams to use Spock or ditch it completely when dealing with Spring, it could become a knock-out criterion for many people.

@leonard84
Copy link
Member

The Spring context is initialized and before the actual Specification instance, the Spock-Spring extension looks at the test class and searches for all fields that are annotated with @SpringBean/@SpyBean as well as the class-level @StubBeans annotations, for all these it creates Proxies in the Spring context. Later on when the spring context receives the test instance those proxies are attached to the specification. Actually the specification instance has separate Mock instances and the proxies just forward all invocations to them.
Keep in mind that the Spring context is cached and reused for the whole Specification. Furthermore, the Mocks receive their interactions by either executing the initializer or the setup method, but both methods could contain references to other fields and even the mocks themselves could reference those fields. Also the setup method could rely on the fact that the spring context has already injected the @Autowired fields, but it can only do so, when the context is already initialized -> catch-22.

@ahlinist you can still use @MockBean from mockito in a Specification, granted it is not as nicely integrated into Spock, but if it solves your problem it is the best solution IMHO.

BUT, I've tested your example and the "similar" test actually does not include the config were the NPE appeared. If you use @SpringBootTest instead of just the subset @ContextConfiguration(classes = { RoboApplication.class, ControllerConfiguration.class}) @WebAppConfiguration you get the same NPE with Mockito.

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class RoboAppTest {

    @MockBean
    GpioController gpioController;

    @Autowired
    TwoWheelRobot twoWheelRobot;

    @Test
    public void testMe() {
        Assert.assertNotNull(twoWheelRobot);
    }
}

@kriegaex
Copy link
Contributor

kriegaex commented Feb 6, 2020

Now I understand better, thank you so much. As I thought, creating a mock or stub like @SpringBean GpioController gpioController = Mock(defaultResponse: ThisResponse.INSTANCE) also does not work for the reasons you described. I want to bother you no further, thanks for your patient explanations. :-)

@farrukhnajmi
Copy link

farrukhnajmi commented Jan 23, 2022

There is currently no way to define behavior outside the scope of the specification and I don't see a good way to add it. So the only Solution would be to refactor the Spring configuration.

@leonard84 Would spock.mock.DetachedMockFactory and MockUtil#attachMock(Object, Specification) and MockUtil#detachMock(Object) be a way to attach the mocks/stubs to the spring context? Also, I am wondering what you meant by "refactor the Spring configuration".

@leonard84
Copy link
Member

@farrukhnajmi that is the old way and it is still supported, but it won't really change the behavior.

@farrukhnajmi
Copy link

farrukhnajmi commented Jan 25, 2022

@leonard84 Its a bit hard for me to know what is the old way and what is the newer / better / recommended way to create a mock/stub outside spec and then attach it to spec. So, DetachedMockFactory and MockUtil#attachMock(Object, Specification) and MockUtil#detachMock(Object) is the old way. Still unclear to me what is the recommended way.

I have been stuck on how to test my springboot controller with only stubbing classes that are external services. Still cannot figure out the best approach that lets me use the spring context mostly like normal but only mocking the beans for the external. Having read this thread I still am unclear what is the recommended best practice to achieve this and what code example if any exists that shows the recommended approach. Thanks for any guidance.

@leonard84
Copy link
Member

@farrukhnajmi without knowing what your concrete problem is, it is hard to help you. The best way would be to put together an MCVE of your problem and post on Stackoverflow or create a new Discussion thread. The being said, if you need to perform actions during context creation, you simply can't use Spock's mocks for that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants