SpringBoot Junit testing for filters in Zuul - microservices

I'm new to Zuul J-unit testing. I have a couple of filters which is ChangeRequestEntityFilter and SessionFilter, Where I pasted my filtercode below. Can someone tell me how to write a Junit for the filter. I've searched and trying to use MockWire for the unit testing(Also I pasted my empty methods with basic annotations and WireMock port). I need at-least one proper example how this J-unit for Zuul works. I've referred the http://wiremock.org/docs/getting-started/ doc. Where I got what to do, but not how to do.
public class ChangeRequestEntityFilter extends ZuulFilter {
#Autowired
private UtilityHelperBean utilityHelperBean;
#Override
public boolean shouldFilter() {
// //avoid http GET request since it does'nt have any request body
return utilityHelperBean.isValidContentBody();
}
#Override
public int filterOrder() {
//given priority
}
#Override
public String filterType() {
// Pre
}
#Override
public Object run() {
RequestContext context = getCurrentContext();
try {
/** get values profile details from session */
Map<String, Object> profileMap = utilityHelperBean.getValuesFromSession(context,
CommonConstant.PROFILE.value());
if (profileMap != null) {
/** get new attributes need to add to the actual origin microservice request payload */
Map<String, Object> profileAttributeMap = utilityHelperBean.getProfileForRequest(context, profileMap);
/** add the new attributes in to the current request payload */
context.setRequest(new CustomHttpServletRequestWrapper(context.getRequest(), profileAttributeMap));
}
} catch (Exception ex) {
ReflectionUtils.rethrowRuntimeException(new IllegalStateException("ChangeRequestEntityFilter : ", ex));
}
return null;
}
}
I know ,I'm asking more. But give me any simple working complete example, I'm fine with it.
My current code with basic annotations and WireMock port.
#RunWith(SpringRunner.class)
#SpringBootTest
#DirtiesContext
#EnableZuulProxy
public class ChangeRequestEntityFilterTest {
#Rule
public WireMockRule wireMockRule = new WireMockRule(8080);
#Mock
ChangeRequestEntityFilter requestEntityFilter;
int port = wireMockRule.port();
#Test
public void changeRequestTest() {
}
}

Have you tried #MockBean?
https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/mock/mockito/MockBean.html
"When #MockBean is used on a field, as well as being registered in the application context, the mock will also be injected into the field. Typical usage might be:"
#RunWith(SpringRunner.class)
public class ExampleTests {
#MockBean
private ExampleService service;
#Autowired
private UserOfService userOfService;
#Test
public void testUserOfService() {
given(this.service.greet()).willReturn("Hello");
String actual = this.userOfService.makeUse();
assertEquals("Was: Hello", actual);
}
#Configuration
#Import(UserOfService.class) // A #Component injected with ExampleService
static class Config {
}
}

Here there is another approach:
private ZuulPostFilter zuulPostFilter;
#Mock
private anotherService anotherService;
#Mock
private HttpServletRequest request;
#Before
public void before() {
MockitoAnnotations.initMocks(this);
MonitoringHelper.initMocks();
zuulPostFilter = new ZuulPostFilter(anotherService);
doNothing().when(anotherService).saveInformation(null, false);
}
#Test
public void postFilterTest() {
log.info("postFilterTest");
RequestContext context = new RequestContext();
context.setResponseDataStream(new ByteArrayInputStream("Test Stream".getBytes()));
context.setResponseGZipped(false);
RequestContext.testSetCurrentContext(context);
when(request.getScheme()).thenReturn("HTTP");
RequestContext.getCurrentContext().setRequest(request);
ZuulFilterResult result = zuulPostFilter.runFilter();
assertEquals(ExecutionStatus.SUCCESS, result.getStatus());
assertEquals("post", zuulPostFilter.filterType());
assertEquals(10, zuulPostFilter.filterOrder());
}
In this case you can test the filter and mock the services inside it without having to autowire it, the problem with the #autowired is that if you have services inside the filter, then it is going to be an integration test that is going to be more difficult to implement.

Related

How do I spring cloud gateway custom filter e2e test?

I have implemented custom GatewayFilterFactory filter. But I don't know how to test this filter with e2e setup.
I have referenced official spring-cloud-gateway AddRequestHeaderGatewayFilterFactoryTests test case code.
This is my custom filter code:
#Component
public class MyCustomFilter implements GatewayFilterFactory<MyCustomFilter.Config>, Ordered {
#Override
public GatewayFilter apply(Config config) {
return new OrderedGatewayFilter((this::filter), getOrder());
}
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
/* do some filtering */
}
#Override
public int getOrder() {
return 1000;
}
#Override
public Config newConfig() {
return new Config(MyCustomFilter.class.getSimpleName());
}
public static getConfig() {
return
}
#Getter
#Setter
public static class Config {
private String name;
Config(String name) {
this.name = name;
}
}
}
And this is my test code:
BaseWebClientTests class look exactly the same as official BaseWebClientTests class code
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = RANDOM_PORT)
#DirtiesContext
#ActiveProfiles("my-custom-filter")
public class MyCustomFilterTests extends BaseWebClientTests {
#LocalServerPort
protected int port = 0;
protected WebTestClient testClient;
protected WebClient webClient;
protected String baseUri;
#Before
public void setup() throws Exception {
setup(new ReactorClientHttpConnector(), "http://localhost:" + port);
}
protected void setup(ClientHttpConnector httpConnector, String baseUri) {
this.baseUri = baseUri;
this.webClient = WebClient.builder().clientConnector(httpConnector)
.baseUrl(this.baseUri).build();
this.testClient = WebTestClient
.bindToServer(httpConnector)
.baseUrl(this.baseUri)
.build();
}
#Test
public void shouldFailByFilterTests() {
/* This test should be failed but success :( */
testClient.get().uri("/api/path")
.exchange().expectBody(Map.class).consumeWith(result -> {
/* do assertion */
});
}
#EnableAutoConfiguration
#SpringBootConfiguration
#Import(DefaultTestConfig.class)
public static class TestConfig {
#Value("${test.uri}")
String uri;
#Bean
public MyCustomFilter myCustomFilter() {
return new MyCustomFilter();
}
#Bean
public RouteLocator testRouteLocator(RouteLocatorBuilder builder, MyCustomFilter myCustomFilter) {
return builder.routes().route("my_custom_filter",
r -> r.path("/api/path")
.filters(f -> f.filter(myCustomFilter.apply(new MyCustomFilter.Config("STRING"))))
.uri(uri))
.build();
}
}
}
Lastly Target controller looks like this:
#RestController
#RequestMapping("/api/path")
public class HttpBinCompatibleController {
#GetMapping("/")
public Mono<BodyData> identity() {
return Mono.just(new BodyData("api success"));
}
#NoArgsConstructor
#AllArgsConstructor
#Getter
static class BodyData {
private String message;
}
}
What I understand how this filter factory test code works is that
custom filter: custom filter is setup inside TestConfig class testRouteLocator method
target controller: target controller is defined as HttpBinCompatibleController class
testClient sends the request, and custom should do some filtering, then target controller should receive the request from testClient.
What I expect from this shouldFailByFilterTests TC is that before request from testClient is sent to target controller, that request should be rejected by MyCustomFilter. But the request is sent to the target controller.
I think the request from testClient is not proxied by testRouteLocator but I'm not sure
Question
What is the cause of this problem?
Is there another way to test my own custom filter?
This problem was related to the version incompatibility between Spring Boot and Spring Cloud.
I was using Spring Boot version 2.1.7 and Spring Cloud version Greenwich.SR2.
Then I found this 'Release train Spring Boot compatibility' table on this link
Before I've noticed version incompatibility, for using #Configuration(proxyBeanMethods = false) feature, upgraded Spring Boot version to 2.2.x.
The solution is using 2.1.x branch BaseWebClientTests class.

Spring boot rest api mockito + mockmvc persistence

I would like to create Test for my rest controller:
#Controller
#RequestMapping("/v2/api/show/project")
public class ApiAccessController {
private final ApiAccessService apiAccessService;
#Autowired
ApiAccessController(ApiAccessService apiAccessService){
this.apiAccessService = apiAccessService;
}
#PutMapping(value = "/{id}/apikey")
public ResponseEntity<ApiKeyResponse> generateApiKey(#PathVariable("id")Long id, Principal principal) {
return apiAccessService.generateApiKey(id, principal.getName());
}
}
My test looks as follow:
#RunWith(SpringJUnit4ClassRunner.class)
public class ApiAccessControllerTest {
private MockMvc mockMvc;
Principal principal = new Principal() {
#Override
public String getName() {
return "TEST_PRINCIPAL";
}
};
#InjectMocks
ApiAccessController apiAccessController;
#Mock
ProjectRepository projectRepository;
#Before
public void setUp(){
mockMvc = MockMvcBuilders.standaloneSetup(apiAccessController).build();
}
#Test
public void testGenerateApiKey() throws Exception {
Project project = new Project();
project.setId((long) 1);
project.setName("test");
project.setDescription("testdesc");
project.setCiid("ciid");
when(projectRepository.save(any(Project.class))).thenReturn(project);
mockMvc.perform(MockMvcRequestBuilders.put("/v2/api/show/project/" + project.getId() +"/apikey").principal(principal))
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
Which is ment to create project and then run generateApiKey on this project, however I get NullpointerException looking like mocked controller cannot find created entity
could anyone please point me in the right direction as I am just starting with testing?
You should mock ApiAccessService instead of ProjectRepository.
Have a look at the code:
#RunWith(SpringJUnit4ClassRunner.class)
public class ApiAccessControllerTest {
private MockMvc mockMvc;
private Principal principal = () -> "TEST_PRINCIPAL";
#InjectMocks
private ApiAccessController apiAccessController;
#Mock
private ApiAccessService apiAccessService;
#Before
public void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(apiAccessController).build();
}
#Test
public void testGenerateApiKey() throws Exception {
long id = 1L;
when(apiAccessService.generateApiKey(id, principal.getName())).thenReturn(new ApiKeyResponse(111L));
mockMvc.perform(MockMvcRequestBuilders.put("/v2/api/show/project/{id}/apikey", id).principal(principal))
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
If you want to create integration test, that tests ApiAccessController -> ApiAccessService -> ProjectRepository integration you need to load your context (use for example #SpringBootTest).
Also you need to fix controller, use ResponseEntity.ok(...) :
#PutMapping(value = "/{id}/apikey")
public ResponseEntity<ApiKeyResponse> generateApiKey(#PathVariable("id") Long id, Principal principal) {
return ResponseEntity.ok(apiAccessService.generateApiKey(id, principal.getName()));
}
You can find really good examples of all test types in this repository MVC tests examples
The Mock you are creating is not referenced in the Controller. The Service you reference in the Controller is not part of your test setup. Therefore any access to the Service will cause a NullPointerException as the Service is not set.

Spring Boot: Testing custom MongoTemplate converters

I'm using this custom converters into my Spring Boot service:
#Configuration
public class MongoConfig {
#Bean
public MongoCustomConversions customConversions(){
List<Converter<?,?>> converters = new ArrayList<>();
converters.add(ReferenceWriterConverter.INSTANCE);
return new MongoCustomConversions(converters);
}
#WritingConverter
enum ReferenceWriterConverter implements Converter<Reference, DBObject> {
INSTANCE;
#Override
public String convert(Reference reference) {
//do stuff
}
}
}
Into my controllers, I'm using MontoTemplate in order to talk with MongoDB. So, all converters are already loaded into template.
However, I'd like to test MongoDbTemplate using Spring injection features. I mean, I want to test MongoDbTemplate using custom converters which should already be loaded.
Any ideas on how it can be achieved?
EDIT
public class ModelTest {
private List<Reference> references;
public ModelTest() {
this.references = new ArrayList<Reference>();
}
#Before
public void setUp() {
Reference reference = new Reference();
reference.setId("Ref1");
reference.setTimestamp(new Date());
Metadata met = new Metadata();
met.setId("Mdt1");
met.setUser("user");
met.setCreationTimestamp(new Date());
met.setMetadata("[{'departament': 'JUST'}]");
reference.setMetadata(met);
this.references.add(reference);
ServerAddress serverAddress = new ServerAddress("127.0.0.1", 27017);
MongoClient mongoClient = new MongoClient(serverAddress);
MongoTemplate mongoTemplate = new MongoTemplate(mongoClient, "db");
mongoTemplate.insert(reference);
}
/**
* Assert Office mime type documents.
*/
#Test
public void office() {
fail("Not yet implemented");
}
}
EDIT 2
I also would like to use custom testing properties. I mean, currently, we are setting properties into src/test/resources/application.properties.
spring.data.mongodb.host: localhost
spring.data.mongodb.port: 27017
How could I load these file properties?
Solution 1
If you want to test it with the Spring context, you can annotate your Test class as SpringBootTest and autowire the MongoTemplate. This should then contain your custom conversions for you to test them:
#RunWith(SpringRunner.class)
#SpringBootTest
public class ModelTest {
private List<Reference> references;
#Autowired
private final MongoTemplate mongoTemplate;
public ModelTest() {
this.references = new ArrayList<Reference>();
}
#Before
public void setUp() {
Reference reference = new Reference();
reference.setId("Ref1");
reference.setTimestamp(new Date());
Metadata met = new Metadata();
met.setId("Mdt1");
met.setUser("user");
met.setCreationTimestamp(new Date());
met.setMetadata("[{'departament': 'JUST'}]");
reference.setMetadata(met);
this.references.add(reference);
mongoTemplate.insert(reference);
}
/**
* Assert Office mime type documents.
*/
#Test
public void office() {
fail("Not yet implemented");
}
}
Solution 2
If you just want to test the converter alone, you could make a ReferenceWriterConverterTest like so:
public class ReferenceWriterConverterTest {
private ReferenceWriterConverter converter;
#Before
public void setUp() {
converter = ReferenceWriterConverter.INSTANCE;
}
//test stuff
}

How to test existing camel endpoints with mock?

Im currently working with Camel's mock component and i would like to test it on an existing routes. Basically i want to retain the existing routes defined in the app, but inject a few mocks during test, to verify or at least peek on the current exchange contents.
Based on the docs and from the Apache Camel Cookbook. I've tried to use #MockEndpoints
Here's the route builder
#Component
public class MockedRouteStub extends RouteBuilder {
private static final Logger LOGGER = LoggerFactory.getLogger(MockedRouteStub.class);
#Override
public void configure() throws Exception {
from("direct:stub")
.choice()
.when().simple("${body} contains 'Camel'")
.setHeader("verified").constant(true)
.to("direct:foo")
.otherwise()
.to("direct:bar")
.end();
from("direct:foo")
.process(e -> LOGGER.info("foo {}", e.getIn().getBody()));
from("direct:bar")
.process(e -> LOGGER.info("bar {}", e.getIn().getBody()));
}
}
Here's my test (currently its a springboot project):
#RunWith(SpringRunner.class)
#SpringBootTest
#MockEndpoints
public class MockedRouteStubTest {
#Autowired
private ProducerTemplate producerTemplate;
#EndpointInject(uri = "mock:direct:foo")
private MockEndpoint mockCamel;
#Test
public void test() throws InterruptedException {
String body = "Camel";
mockCamel.expectedMessageCount(1);
producerTemplate.sendBody("direct:stub", body);
mockCamel.assertIsSatisfied();
}
}
message count is 0 and it looks more like #MockEndpoints is not triggered.
Also, logs indicate that the log is triggered
route.MockedRouteStub : foo Camel
An alternative i've tried is to use an advice:
...
#Autowired
private CamelContext context;
#Before
public void setup() throws Exception {
context.getRouteDefinitions().get(0).adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
mockEndpoints();
}
});
}
The startup logs indicate that advice is in place:
c.i.InterceptSendToMockEndpointStrategy : Adviced endpoint [direct://stub] with mock endpoint [mock:direct:stub]
But still my test fails with the message count = 0.
Posting the answer which worked for the setup that i have.
Without any changes to the RouteBuilder, the Test would look something like this:
#RunWith(CamelSpringBootRunner.class)
#SpringBootTest
#MockEndpoints
public class MockedRouteStubTest {
#Autowired
private ProducerTemplate producerTemplate;
#EndpointInject(uri = "mock:direct:foo")
private MockEndpoint mockCamel;
#Test
public void test() throws InterruptedException {
String body = "Camel";
mockCamel.expectedMessageCount(1);
producerTemplate.sendBody("direct:stub", body);
mockCamel.assertIsSatisfied();
}
}

test spring 5 reactive rest service

I'm using SpringBoot 2 and Spring 5 (RC1) to expose reactive REST services. but I can't manage to write unit test for those controllers.
Here is my controller
#Api
#RestController
#RequestMapping("/")
public class MyController {
#Autowired
private MyService myService;
#RequestMapping(path = "/", method = RequestMethod.GET)
public Flux<MyModel> getPages(#RequestParam(value = "id", required = false) String id,
#RequestParam(value = "name", required = false) String name) throws Exception {
return myService.getMyModels(id, name);
}
}
myService is calling a database so I would like not to call the real one. (I don't wan't integration testing)
Edit :
I found a way that could match my need but I can't make it work :
#Before
public void setup() {
client = WebTestClient.bindToController(MyController.class).build();
}
#Test
public void getPages() throws Exception {
client.get().uri("/").exchange().expectStatus().isOk();
}
But I'm getting 404, seems it can't find my controller
You have to pass actual controller instance to bindToController method.
As you want to test mock environment, you'll need to mock your dependencies, for example using Mockito.
public class MyControllerReactiveTest {
private WebTestClient client;
#Before
public void setup() {
client = WebTestClient
.bindToController(new MyController(new MyService()))
.build();
}
#Test
public void getPages() throws Exception {
client.get()
.uri("/")
.exchange()
.expectStatus().isOk();
}
}
More test examples you can find here.
Also, I suggest switching to constructor-based DI.

Resources