I am trying to use Wiremock to intercept an HTTP call fired on ApplicationReadyEvent. The issue is that this call is made before a Wiremock rules being applied. So this API is no mocked.
See following example
public class OnApplicationReadyListener implements ApplicationListener<ApplicationReadyEvent>{
#Override
public void onApplicationEvent(ApplicationReadyEvent event) {
// in this pathe is being send a request
consulHealthCheckService.register();
}
}
The stubbing is configured in #Before phase, which is actually triggered once a Servlet container is ready. So actually - after the HTTP call is fired
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = ConsulApplication.class)
public class ConsulApplicationTest {
#Rule
public WireMockRule wireMockRule = new WireMockRule(Agent.DEFAULT_PORT );
#Before
public void setup(){
// is being executed after the http call was fired
stubFor(get(anyUrl()).willReturn(aResponse().withStatus(HttpStatus.OK.value())));
stubFor(put(urlEqualTo("/agent/service/register"))
.willReturn(aResponse()
.withStatus(HttpStatus.OK.value())
.withHeader("Content-Type", APPLICATION_JSON_VALUE)
));
}
#Test
public void shouldRegisterServiceOnApplicationStartup(){
verify( putRequestedFor(urlEqualTo("/agent/service/register")));
}
}
Is there any way how to stub given http call? Please note: Iam not able to mock the service code which actually triggers the call.
Instead of using the WireMockRule, you could statically create a WireMockServer start it on the port, and load the mappings even before this test class is fully initialized. IF this does work, you could ensure the server is shutdown in an #AfterClass method.
If you try that and it's still making the request before your test class is initialized, then you'll have to figure out when/where that request is made and find another way to test your assertion as it's being fired before the test class is even initialized.
Related
In my tests I setup the MockMvc object in the #Before like this
mockMvc = MockMvcBuilders.webAppContextSetup(context)
.apply(springSecurity())
.build();
In every request I do I always need to send the same headers.
Is there a way to configure the headers the MockMvc will use globally or per test class?
I do not know if it still relevant, but I stumbled over the same problem.
We added an API key authentication to a REST api afterwards, and all tests (mainly with #AutoConfigureMockMvc) needed to be adjusted with using a proper API (on top of the new tests, testing that the keys are working).
Spring uses their Customizers and Builders pattern also when creating the MockMvc, like it is done with RestTemplateBuilder and RestTemplateCustomizer.
You are able to create a #Bean/#Component that is a org.springframework.boot.test.autoconfigure.web.servlet.MockMvcBuilderCustomizerand it will get picked up during the bootstrap process of your #SpringBootTests.
You can then add a parent defaultRequetsBuilders that are merged with the specific RequestBuilders when running the test.
Sample Customizer that adds a header
package foobar;
import org.springframework.boot.test.autoconfigure.web.servlet.MockMvcBuilderCustomizer;
import org.springframework.stereotype.Component;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
/**
* Whenever a mockmvc object is autoconfigured, this customizer should be picked up, and a default, usable, working, valid api key is set as
* default authorization header to be applied on all tests if not overwritten.
*
*/
#Component
public class ApiKeyHeaderMockMvcBuilderCustomizer implements MockMvcBuilderCustomizer {
#Override
public void customize(ConfigurableMockMvcBuilder<?> builder) {
// setting the parent (mergeable) default requestbuilder to ConfigurableMockMvcBuilder
// every specifically set value in the requestbuilder used in the test class will have priority over
// the values set in the parent.
// This means, the url will always be replaced, since "any" would not make any sense.
// In case of multi value properties (like headers), existing headers from our default builder they are either merged or appended,
// exactly what we want to achieve
// see https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcBuilderCustomizer.html
// and https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/Mergeable.html
RequestBuilder apiKeyRequestBuilder = MockMvcRequestBuilders.get("any")
.header("api-key-header", "apikeyvalue");
builder.defaultRequest(apiKeyRequestBuilder);
}
}
Hope that helps.
How about you make a factory class to start you off with your already decrorated-with-headers request? Since MockHttpServletRequestBuilder is a builder, you just decorate the request with any of the additional properties (params, content type, etc.) that you need. The builder is designed just for this purpose! For example:
public class MyTestRequestFactory {
public static MockHttpServletRequestBuilder myFactoryRequest(String url) {
return MockMvcRequestBuilders.get(url)
.header("myKey", "myValue")
.header("myKey2", "myValue2");
}
}
Then in your test:
#Test
public void whenITestUrlWithFactoryRequest_thenStatusIsOK() throws Exception {
mockMvc()
.perform(MyTestRequestFactory.myFactoryRequest("/my/test/url"))
.andExpect(status().isOk());
}
#Test
public void whenITestAnotherUrlWithFactoryRequest_thenStatusIsOK() throws Exception {
mockMvc()
.perform(MyTestRequestFactory.myFactoryRequest("/my/test/other/url"))
.andExpect(status().isOk());
}
Each test will call the endpoint with the same headers.
You can write an implementation of javax.servlet.Filter. In your case, you can add the headers into your request. MockMvcBuilders has a method to add filters:
mockMvc = MockMvcBuilders.webAppContextSetup(context)
.apply(springSecurity())
.addFilter(new CustomFilter(), "/*")
.build();
this.mockMvc = MockMvcBuilders.webAppContextSetup(context).apply(new HttpHeaderMockMvcConfigurer()).build();
public class HttpHeaderMockMvcConfigurer extends MockMvcConfigurerAdapter {
#Override
public RequestPostProcessor beforeMockMvcCreated(ConfigurableMockMvcBuilder<?> builder, WebApplicationContext cxt) {
builder.defaultRequest(MockMvcRequestBuilders.post("test").header("appId", "aaa"));
return super.beforeMockMvcCreated(builder, cxt);
}
}
Define default request properties that should be merged into all performed requests. In effect this provides a mechanism for defining common initialization for all requests such as the content type, request parameters, session attributes, and any other request property.
I'm using Spring 4.3.8.RELEASE with JUnit 4.12 and Mockito 1.10.18. I have a service that publishes events ...
#Service("organizationService")
#Transactional
public class OrganizationServiceImpl implements OrganizationService, ApplicationEventPublisherAware
publisher.publishEvent(new ZincOrganizationEvent(id));
#Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher)
{
this.publisher = publisher;
}
...
#Override
public void save(Organization organization)
{
...
publisher.publishEvent(new ThirdPartyEvent(organization.getId()));
My question is, how do I verify in a JUnit test that an event has actually been published?
#Test
public void testUpdate()
{
m_orgSvc.save(org);
// Want to verify event publishing here
I prefer the opposite approach, which is more integration test-ey:
🧙‍♂️Mock the ApplicationListener using Mockito
đź”—Register the mock application listener onto the ConfigurableApplicationContext
🔨Do work
✔Verify the mock has received the event
With this approach you are testing that an event has been published by means that someone is receiving it.
Here is the code of a rudimental authentication test. Among other conditions, I test that a login event has occurred
#Test
public void testX509Authentication() throws Exception
{
ApplicationListener<UserLoginEvent> loginListener = mock(ApplicationListener.class);
configurableApplicationContext.addApplicationListener(loginListener);
getMockMvc().perform(get("/").with(x509(getDemoCrt())))//
.andExpect(status().is3xxRedirection())//
.andExpect(redirectedUrlPattern("/secure/**"));
getErrorCollector().checkSucceeds(() -> {
verify(loginListener, atLeastOnce()).onApplicationEvent(any(UserLoginEvent.class));
return null;
});
}
My advice is to unleash the power of Mockito to deeply verify the event arguments. In my case, I will extend my code to:
Check that the username in the login event matches the authenticated principal
Perform additional tests where the user blatantly fails to login and I will expect one of the various login failure events
If you want to test if you didn't forget to call publishEvent method inside your OrganizationServiceImpl you can use something like this:
class OrganizationServiceImplTest {
private OrganizationServiceImpl organizationService;
private ApplicationEventPublisher eventPublisher;
#Before
public void setUp() {
eventPublisher = mock(ApplicationEventPublisher.class);
organizationService = new OrganizationServiceImpl();
organizationService.setApplicationEventPublisher(eventPublisher)
}
#Test
public void testSave() {
/* ... */
organizationService.save(organization);
verify(eventPublisher).publishEvent(any(ThirdPartyEvent.class));
}
}
Test case above will verify whether or not there was an invocation of publishEvent method.
For more check the documentation.
Regarding:
My question is, how do I verify in a JUnit test that an event has actually been published?
You have to test ApplicationEventPublisher implementation and probably without mocks if you want to verify actual sending.
I have the following route configuration:
#Component
public class MyRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
from("direct:in").to("direct:out");
}
}
When I try to test it:
#RunWith(CamelSpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { MyRouteTest.TestConfig.class }, loader = CamelSpringDelegatingTestContextLoader.class)
#MockEndpoints
public class MyRouteTest {
#EndpointInject(uri = "mock:direct:out")
private MockEndpoint mockEndpoint;
#Produce(uri = "direct:in")
private ProducerTemplate producerTemplate;
#Configuration
public static class TestConfig extends SingleRouteCamelConfiguration {
#Bean
#Override
public RouteBuilder route() {
return new MyRoute();
}
}
#Test
public void testRoute() throws Exception {
mockEndpoint.expectedBodiesReceived("Test Message");
producerTemplate.sendBody("Test Message");
mockEndpoint.assertIsSatisfied();
}
}
I get this exception:
org.apache.camel.component.direct.DirectConsumerNotAvailableException:
No consumers available on endpoint: Endpoint[direct://out].
Exchange[Message: Test Message]
It looks like the Mock is not picking up the message from the endpoint.
What am I doing wrong?
The problem is that mock endpoints just intercept the message before delegating to the actual endpoint. Quoted from the docs:
Important: The endpoints are still in action. What happens differently
is that a Mock endpoint is injected and receives the message first and
then delegates the message to the target endpoint. You can view this
as a kind of intercept and delegate or endpoint listener.
The solution to your problem is to tell certain endpoints (the ones that expect a consumer in your case) not to delegate to the actual endpoint. This can easily be done using #MockEndpointsAndSkip instead of #MockEndpoints:
#RunWith(CamelSpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { MyRouteTest.TestConfig.class }, loader = CamelSpringDelegatingTestContextLoader.class)
#MockEndpointsAndSkip("direct:out") // <-- turns unit test from red to green ;)
public class MyRouteTest {
// ....
}
This issue because, in your route configuration, there is no route with "direct:out" consumer endpoint.
add a line like some thing below,
from("direct:out").("Anything you want to log");
So that direct:out will consume the exchange and In your test, mock will be able check the received text without any issues. Hope this helps !!
I have a simple controller defined as below:
#Controller
#RequestMapping("/rest/tests")
public class TestController {
#Autowired
private ITestService testService;
#RequestMapping(value="/{id}", method=RequestMethod.DELETE)
#ResponseStatus(value = HttpStatus.OK)
public void delete(#PathVariable Integer id)
{
Test test = testService.getById(id);
testService.delete(test);
}
}
I have been trying to test the delete method, and have not succeeded so far. The test I have written is pretty simple too.
public class MockmvcTest {
#InjectMocks
private TestController test;
private MockMvc mockMvc;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(test).build();
}
#Test
public void myTest() throws Exception {
this.mockMvc.perform(delete("/rest/tests/{id}", new Integer(4)))
.andExpect(status().isOk()
}
}
I have tested the method using "advanced rest client" extension in chrome and it works as expected.
By working I mean that the entity with the given id is deleted from database.
When myTest() is executed the status code is still 200, but the entity is not removed from the database.
What might be the reason behind this behavior?
You're using Mockito to inject mock service beans into your TestController (in particular, ITestService). All mocks by definition have no behaviour until you specify it, by default all operations you perform will either do nothing or return null. You can easily confirm that by setting a breakpoint inside TestController.delete method, executing the test in debug mode and inspecting values of test and testService variables.
Mockito is used for unit-level tests that replace SUT's collaborators with a mock that you set up to behave in a certain verifiable way. Once you call a method on your SUT (in your case that's TestController) you can assert whether it adheres to its contract or not.
It's actually a big no-no to allow your automated tests to modify a real instance of a database.
I have seen example , how to call spring controller using mockito.
Using Mock I call Spring MVC controller.
Controller Invokes Spring service class.
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/spring/root-context.xml" })
public class TestController {
#Mock
private TestService testService;
#InjectMocks
private PaymentTransactionController paymentController;
private MockMvc mockMvc;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.setMockMvc(MockMvcBuilders.standaloneSetup(paymentController).build());
}
#Test
public void test() throws Exception {
this.mockMvc.perform(post("/tr/test").content(...)).andExpect(status().isOk());
// testService.save(); <-- another way
}
Ok it works well. I calls my Spring controller very well. But In Spring controller I have Injected Service Layer.
#Autowired
private TestService serviceTest;
#RequestMapping(value = "/test", method = RequestMethod.POST)
#ResponseBody()
public String test(HttpServletRequest request) {
...
serviceTest.save();
// in save method I call dao and dao perist data;
// I have injected dao intrface in serviceTest layer
...
return result;
}
The problem is that, my app does not invokes save method, it is not entered in it. I have no error too. The same result is when I call save() method from Junit (I have commented it in test() method).
When I debug, I have seen that interrupt method happens of org.mockito.internal.creation.MethodInterceptorFilter
How to solve this problem? what happens?
If you are doing a unit test of your controller, you should mock the service layer (what you are doing). In this kind of test, you just control that :
the correct methods of the controller are triggered and they produce what is expected
the correct methods in service layer are called ... in the mock
You simply have to configure the return values of the methods of the mock (if relevant), or control what was called
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.setMockMvc(MockMvcBuilders.standaloneSetup(paymentController).build());
// set return values from the mocked service
when(testService.find(1)).thenReturn(...);
}
and verify later what has been called
#Test
public void test() throws Exception {
this.mockMvc.perform(post("/tr/test").content(...)).andExpect(status().isOk());
// testService.save(); <-- another way
verify(testService, times(1)).save();
}
If you want to do an integration test, you do not mock the service, but setup an application context to inject real beans, but ordinarily use an embedded database instead of the real one.
just change #InjectMocks to #Autowired. This fix the issue! In this case you are not mocking, you are invoking method with real data.
As I understand, you perform post to "/tr/test" resource, but request mapping in your controller is '/payment'. Make sure you post to resource mapped in the controller.