I'm implementing a SchedulerService
that uses a AgentRestClient
bean to get some data from an external system. It looks something like this:
@Service
public class SchedulerService {
@Inject
private AgentRestClient agentRestClient;
public String updateStatus(String uuid) {
String status = agentRestClient.get(uuid);
...
}
...
}
To test this service, I'm using Cucumber while at the same time I'm trying to mock the behavior of AgentRestClient
using Spring Boot's @MockBean
annotation, as follows:
import cucumber.api.CucumberOptions;
import cucumber.api.java.Before;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = CentralApp.class)
@CucumberOptions(glue = {"com.company.project.cucumber.stepdefs", "cucumber.api.spring"})
public class RefreshActiveJobsStepDefs {
@MockBean
private AgentRestClient agentRestClient;
@Inject
private SchedulerService schedulerService;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
given(agentRestClient.get(anyString())).willReturn("FINISHED");//agentRestClient is always null here
}
//Skipping the actual Given-When-Then Cucumber steps...
}
When I try to run any Cucumber scenario, the agentRestClient
is never mocked/injected. The setUp()
method fails with a NPE:
java.lang.NullPointerException
at com.company.project.cucumber.stepdefs.scheduler.RefreshActiveJobsStepDefs.setUp(RefreshActiveJobsStepDefs.java:38)
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 cucumber.runtime.Utils$1.call(Utils.java:37)
at cucumber.runtime.Timeout.timeout(Timeout.java:13)
at cucumber.runtime.Utils.invoke(Utils.java:31)
at cucumber.runtime.java.JavaHookDefinition.execute(JavaHookDefinition.java:60)
at cucumber.runtime.Runtime.runHookIfTagsMatch(Runtime.java:223)
at cucumber.runtime.Runtime.runHooks(Runtime.java:211)
at cucumber.runtime.Runtime.runBeforeHooks(Runtime.java:201)
at cucumber.runtime.model.CucumberScenario.run(CucumberScenario.java:40)
at cucumber.runtime.model.CucumberFeature.run(CucumberFeature.java:165)
at cucumber.runtime.Runtime.run(Runtime.java:121)
at cucumber.api.cli.Main.run(Main.java:36)
at cucumber.api.cli.Main.main(Main.java:18)
To get to this point I followed the following 2 resources, but still no luck getting it working:
The culprit seems to be Cucumber integration into Spring, because when I try the same approach with a plain JUnit @Test
method, mocking works as expected.
So could you please tell me what Cucumber or Spring configuration have I missed or misunderstood?
Thanks, Bogdan
@3wj's approach works for me. In my case, the bean is optionally inject into a controller. Looks in this case, @MockBean will not work, I guess the reason is the bean is not hard referenced. Add a additional annotation @Autowired to make the bean hard referenced, then Spring will initialize the bean.
@RestController
public class MyController {
public MyController(@Autowired(required = false) IMyService myService) {
this.myService = myService;
}
@GetMapping("/the/path")
public ResponseEntity<String> getData() {
if(this.myService==null){
//Throw service unavaillable exception
}
String data = this.myService.getData();
return new ResponseEntity<>(data, HttpStatus.OK);
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class MyControllerIT {
@Value("${local.server.port}")
private int port;
private RestTemplate restTemplate;
@Autowired
@MockBean
private IMyService myService
@Test
public void testQuery() throws Exception {
// restTemplate to call rest API....
}
}
OK, so I found out that the @MockBean
annotation is ignored because I was running the tests with Cucumber instead of running them through Spring Boot. Duh...
So I replaced @MockBean
with @Mock
, and then I manually inject that mock into my service layer.
So now my test looks like this:
@SpringBootTest(classes = CentralApp.class)
@ContextConfiguration
public class RefreshActiveJobsStepDefs {
@Inject
private SchedulerService schedulerService;
@Mock
private AgentRestClient agentRestClient;
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
given(agentRestClient.get(anyString())).willReturn("FINISHED");
schedulerService.setAgentRestClient(agentRestClient);
}
//Skipping the actual Given-When-Then Cucumber steps...
}
As you can see I've also removed the @CucumberOptions(glue=...)
annotation, and now I make sure to pass it though the runner, which for CLI would be by using the --glue
option.
I hope this helps.
@MockBean is not ignored, the bean is mocked but it's not injected, so you can inject it via @inject, this works fine for me :
@Inject
@MockBean
private AgentRestClient agentRestClient;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With