-
-
Notifications
You must be signed in to change notification settings - Fork 2k
Use TestContextManager in cucumber-spring #1470
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
Comments
@MockBean
annotations in cucumber-spring
Another Spring 5 issue: #1315 (was Closed due to inactivity) |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed in a week if no further activity occurs. |
This issue has been automatically closed because of inactivity. You can support the Cucumber core team on opencollective. |
StaleBot take a break. |
Another consideration. Currently a dummy step is required in the context configuration. This is kinda silly. @SpringBootTest(classes=Application.class)
@AutoConfigureMockMvc
@TestPropertySource("classpath:test.properties")
public class CucumberContextConfiguration {
@Value("${key}")
private String value;
@Before
public void setup_cucumber_spring_context(){
// Dummy method so cucumber will recognize this class as glue
// and use its context configuration.
}
} In Cucumber v5 object factories will be shared between backends. So it should be possible to make a spring backend that scans the glue path for context configuration and adds it to the object factory. |
In preparation of #1470 constructor dependency injection is deprecated in favour of field injection with @Autowired.
So #1604 contains a pretty good spike. I've tested it with a a few |
What more is needed to have it ready? I updated Is it a must to have no arguments constructor in classes with cucumber annotations? With constructor dependency injection things are easier to test. Also, no argument constructor requirement concerns not only step definitions, but all classes with Cucumber annotations. |
Heya, thanks for looking into and updating the branch with master! The main problem I ran into is that the So for Cucumber this is a typical test context: public class UserSteps {
@MockBean
private User object;
@Given("there is a User")
public void there_is_a_User() {
when(object.toString()).thenReturn("I've been mocked");
}
@Then("the User is mocked")
public void the_user_is_mocked() throws Exception {
assertThat(object.toString(), equalTo("I've been mocked"));
}
}
public class SystemSteps {
@MockBean
private System object;
@Given("there is a System")
public void there_is_a_System() {
when(object.toString()).thenReturn("I've been mocked");
}
@Then("the System is mocked")
public void the_user_is_mocked() throws Exception {
assertThat(object.toString(), equalTo("I've been mocked"));
}
} In the the However this multiplexing is intentionally not complete. Some This attribute store contains state and is because of that a potential problem. For example the @Override
public void afterTestMethod(TestContext testContext) throws Exception {
if (Boolean.TRUE.equals(testContext.getAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE))) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Resetting RequestContextHolder for test context %s.", testContext));
}
RequestContextHolder.resetRequestAttributes();
testContext.setAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE,
Boolean.TRUE);
}
testContext.removeAttribute(POPULATED_REQUEST_CONTEXT_HOLDER_ATTRIBUTE);
testContext.removeAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE);
} Fortunately this example has been properly implemented. Multiple executions of DependencyInjectionTestExecutionListener
DirtiesContextBeforeModesTestExecutionListener
DirtiesContextTestExecutionListener
MockMvcPrintOnlyOnFailureTestExecutionListener
MockRestServiceServerResetTestExecutionListener
MockitoTestExecutionListener
ResetMocksTestExecutionListener
RestDocsTestExecutionListener
ServletTestExecutionListener
SpringBootDependencyInjectionTestExecutionListener
SqlScriptsTestExecutionListener
TransactionalTestExecutionListener
WebDriverTestExecutionListener To further complicate this
Yes. Unfortunately so. As you can see in However you can still inject dependencies by annotating fields with
For #2 a possible solution would be the introduction of a public class UserSteps {
@MockBean
private User object;
@CucumberGlue
private Shared shared;
}
public class SystemSteps {
@MockBean
private System object;
@CucumberGlue
private Shared shared;
}
public class SharedSteps {
@AutoWired
private final SomeComponent component
} The challenge here is that classes containing This wasn't possible in Cucumber v4.x. It should be possible in v5.x. I haven't had the time to revisit this yet. You would be most welcome to do so. |
Btw. If you are unable or not interested in spending time on this, I will also accept an PR against |
Thanks for explanation. I have a broader view on this now. I would definitely like to and will spend more time on this. For now I'll send a PR against |
I don't see a reason to invoke anything but Reviewing the code in the test execution listeners below I also don't believe anything useful would happen if we were to provide a dummy test instance and method. It's the annotations on the method that drive most of the functionality.
|
Is there any workarounf for this in the meantime? |
If you're using @CucumberContextConfiguration
@SpringBootTest(classes = TestConfig.class)
public class CucumberSpringConfiguration {
@MockBean
public MyService service;
} The class annotated with You'll have to figure out the edgecase yourself. |
Thank you @mpkorstanje, when will the 6.x version be GA? |
Is there an update on this? I've been using the workaround mentioned above, but i would like to use @MockBean in my stepdef classes for better readability of the code. |
I've been using a workaround where I put fields annotated with But this is not the best thing to do because I have a lot of mocks in my single Is there still no way to use |
Unlike JUnit where in one execution tests from multiple classes can be ran, Cucumber runs scenarios from multiple feature files. This means that step definitions must be globally available. There is no way to scope them to a specific feature file and thus no way to use different sets of mock beans. Your best option right now is to either reconsider your motivations for mocking. Instead consider stubbing your external services. Or you may want to consider multiple executions of Cucumber with different context configurations for Spring. |
To make writing tests with Spring easier Spring provides a `TestContextManager`. This classes provides call backs for various `TestExecutionListeners`. These are then used by various extensions such as the `MockitoTestExecutionListener` which injects `@MockBeans` into test instances. When all methods are not invoked this leads to problems such as (#2654,#2655,#2656) While this was initially (#1470) not a problem, it appears that various listener implementations have started to assume that all methods would be invoked. Closes: #2655 Fixes: #2654, #2572
To make writing tests with Spring easier Spring provides a `TestContextManager`. This classes provides call backs for various `TestExecutionListeners`. These are then used by various extensions such as the `MockitoTestExecutionListener` which injects `@MockBeans` into test instances. When all methods are not invoked this leads to problems such as (#2654,#2655,#2656) While this was initially (#1470) not a problem, it appears that various listener implementations have started to assume that all methods would be invoked. Closes: #2655 Fixes: #2654, #2572
To make writing tests with Spring easier Spring provides a `TestContextManager`. This classes provides call backs for various `TestExecutionListeners`. These are then used by various extensions such as the `MockitoTestExecutionListener` which injects `@MockBeans` into test instances. When all methods are not invoked this leads to problems such as (#2654,#2655,#2656) While this was initially (#1470) not a problem, it appears that various listener implementations have started to assume that all methods would be invoked. Closes: #2655 Fixes: #2654, #2572
To make writing tests with Spring easier Spring provides a `TestContextManager`. This classes provides call backs for various `TestExecutionListeners`. These are then used by various extensions such as the `MockitoTestExecutionListener` which injects `@MockBeans` into test instances. When all methods are not invoked this leads to problems such as (#2654,#2655,#2656) While this was initially (#1470) not a problem, it appears that various listener implementations have started to assume that all methods would be invoked. Closes: #2655 Fixes: #2654, #2572
To make writing tests with Spring easier Spring provides a `TestContextManager`. This classes provides call backs for various `TestExecutionListeners`. These are then used by various extensions such as the `MockitoTestExecutionListener` which injects `@MockBeans` into test instances. When all methods are not invoked this leads to problems such as (#2654,#2655,#2656) While this was initially (#1470) not a problem, it appears that various listener implementations have started to assume that all methods would be invoked. Closes: #2655 Fixes: #2654, #2572
Hi ! Thanks a lot |
Please create a new issue. It would also help if you can create a minimal reproducer. And even better if you can analyze the problem by putting breakpoints in all these classes: |
Summary
Support
TestContextManager
incucumber-spring
.Expected Behavior
When using
cucumber-spring
@MockBean
(and other annotations) should just work.Current Behavior
It is currently possible to use
@Autowired
e.g.:But fields annotated with
@MockBean
are not mocked, the example fails with a null pointer exception.Possible Solution
The causes of failure are two fold:
There is no spring context declared (
ContextConfiguration
,BootstrapWith
, ect) so theSpringFactory
falls back to some minimal default. This can be resolved by adding@SpringBootTest
to the step definition.The SpringFactory does not call
TestContextManager.beforeTestClass()
TestContextManager.prepareTestInstance()
.TestContextManager.beforeTestMethod()
TestContextManager.beforeTestExecution()
TestContextManager.afterTestExecution()
TestContextManager.afterTestMethod()
TestContextManager.afterTestClass()
Instead step definitions are registered as beans in the Spring context. This had the advantage that step definitions can be auto wired into each other and constructor dependency injection just works. However it also puts the step definitions into the spring context which complicates things.
Given that step definitions calling each other is not a best practice I would not mind step definitions were no longer part of the spring context and instead treated as test instances (see SpringJUnit4ClassRunner.createTest). Any dependency injection would have to be done via
@Autowired
.So to implement this properly the following would have to be done:
TestContextManager
to that of theObjectFactory
.Your Environment
cucumber-spring:4.0.0
The text was updated successfully, but these errors were encountered: