Spring Feign configuration: how to test all #Bean methods are called - java

I want to configure Spring feign with a configuration class, and I want to make sure that all the #Bean methods are called when Spring configures the feign client for me.
How to test it?
For example, I have:
#FeignClient(
name = "PreAuthSendRequest",
url = "${xxx.services.preauth.send.url}",
configuration = AppFeignConfig.class)
public interface RequestService {
#PostMapping("")
#Headers("Content-Type: application/json")
PreAuthResponse execute(#RequestBody PreAuthRequest preAuthRequest);
}
And AppFeignConfig.java:
#Configuration
#RequiredArgsConstructor
public class AppFeignConfig{
private final HttpClient httpClient;
private final Jackson2ObjectMapperBuilder contextObjectMapperBuilder;
#Bean
public ApacheHttpClient client() {
return new ApacheHttpClient(httpClient);
}
#Bean
public Decoder feignDecoder() {
return new JacksonDecoder((ObjectMapper)contextObjectMapperBuilder.build());
}
#Bean
public Encoder feignEncoder() {
return new JacksonEncoder((ObjectMapper)contextObjectMapperBuilder.build());
}
#Bean
public Retryer retryer() {
return Retryer.NEVER_RETRY;
}
#Bean
public ErrorDecoder errorDecoder() {
return new ServiceResponseErrorDecoder();
}
}
So, how to verify that all #Bean methods are called? I know #MockBean, but what I want to check is config.feignDecoder(), etc., are indeed called.
When I am trying to context.getBean(RequestService.class); and call execute() method, it seems to send a real request and without wiremock, it fails, obviously.

For now I have this:
#SpringBootTest
#ExtendWith(SpringExtension.class)
#ActiveProfiles("test")
class RequestServiceTest {
#Autowired
private ApplicationContext applicationContext;
#MockBean
private ApacheHttpClient client;
#MockBean
private Decoder feignDecoder;
#MockBean
private Encoder feignEncoder;
#MockBean
private Retryer retryer;
#MockBean
private ErrorDecoder errorDecoder;
#Test
void shouldRetrieveBeansFromApplicationContextToConstructConfigurationInstance() {
AppFeignConfig config = applicationContext.getBean(AppFeignConfig.class);
Assertions.assertEquals(config.feignEncoder(), feignEncoder);
Assertions.assertEquals(config.feignDecoder(), feignDecoder);
Assertions.assertEquals(config.errorDecoder(), errorDecoder);
Assertions.assertEquals(config.client(), client);
Assertions.assertEquals(config.retryer(), retryer);
}
}
I don't know if it is how it should be. If any idea, please comment.

Related

Unable to wire in dependency using MockBean in WebMVCTest

I have a controller class:
public class Controller {
private final IProcessor processor;
public Controller (final ProcessorFactory factory) {
this.processor = factory.getInstance();
}
}
A Factory class to provide the different instances of IProcessor:
#Component
public class ProcessorFactory {
private final Dep1 dep1;
private final Dep2 dep2;
public ProcessorFactory (final Dep1 dep1,
final Dep2 dep2) {
this.dep1= dep1;
this.dep2= dep2;
}
public IProcessor getInstance() {
if (...) {
return new ProcessorA(dep1, dep2);
}
return new ProcessorB(dep1, dep2);
}
}
In my mockito test class where I use Junit5, I am not able to instantiate the IProcessor member and is null:
#WebMvcTest(Controller.class)
public class ControllerTest {
#MockBean
private ProcessorFactory processorFactory ;
#MockBean
private IProcessor processor;
#Autowired
private MockMvc mockMvc;
#Test
public void test1() throws Exception {
when(processor.process(any(Request.class), any(String.class)))
.thenReturn(new BlaBla("Test", "Test"));
String request = ...
this.mockMvc.perform(post("/test/test").contentType(MediaType.APPLICATION_JSON).content(request))
.andDo(print())
.andExpect(status().is2xxSuccessful());
}
}
I am not sure I am using MockBean correctly. Basically I want to mock both the Factory and the Processor.
Since you need to call a mocked method (getInstance()) during Spring context initialization (inside the Controller's constructor), you need to mock the said method in a different way. The mocked bean has to be not only provided as an existing object, but also it should have it's mocked behavior defined.
Addtionally, IProcessor implementations are not configured as Spring beans, so Spring will not inject them - ProcessorFactory calls new explicitly and creates the objects without Spring involvement.
I've created a simple project to reproduce your problem and provide a solution - you can find it here on GitHub if you want to check if the whole thing is working, but here's the most important test snippet (I've simplified the methods a bit):
#WebMvcTest(Controller.class)
class ControllerTest {
private static final IProcessor PROCESSOR = mock(IProcessor.class);
#TestConfiguration
static class InnerConfiguration {
#Bean
ProcessorFactory processorFactory() {
ProcessorFactory processorFactory = mock(ProcessorFactory.class);
when(processorFactory.getInstance())
.thenReturn(PROCESSOR);
return processorFactory;
}
}
#Autowired
private MockMvc mockMvc;
#Test
void test1() throws Exception {
String result = "this is a test";
when(PROCESSOR.process(any()))
.thenReturn(result);
mockMvc.perform(post("/test/test")
.contentType(MediaType.APPLICATION_JSON)
.content("{}"))
.andDo(print())
.andExpect(status().is2xxSuccessful())
.andExpect(content().string(result));
}
}

Configuration Getting Ignored in Test

I try to test my spring app but encounter following problem:
In "normal mode"(mvn spring-boot:run) the app starts as expected and adapterConfig gets set and is NOT NULL. When I start my testclass to test the MVC, adapterConfig does not get set. Spring ignores the whole config class.
test:
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = StudentController.class)
public class StudentControllerTests {
#Autowired
private MockMvc mockMvc;
#MockBean
private StudentService service;
#MockBean
private StudentRepository repository;
#Test
public void shouldReturnABC() throws Exception{
MvcResult result = this.mockMvc.perform(get("/students/abc")).andReturn();
}
}
controller:
#RestController
#RequestMapping("/students")
#PermitAll
public class StudentController {
#Autowired
StudentService studentService;
//get
#GetMapping("/abc")
public String abc (){
return "abc";
}
config:
#Configuration
public class SpringBootKeycloakConfigResolver implements KeycloakConfigResolver {
private KeycloakDeployment keycloakDeployment;
private AdapterConfig adapterConfig;
#Autowired
public SpringBootKeycloakConfigResolver(AdapterConfig adapterConfig) {
this.adapterConfig = adapterConfig;
}
#Override
public KeycloakDeployment resolve(OIDCHttpFacade.Request request) {
if (keycloakDeployment != null) {
return keycloakDeployment;
}
keycloakDeployment = KeycloakDeploymentBuilder.build(adapterConfig);
return keycloakDeployment;
}
}
adapterConfig is null when hitting the test but gets set & created when hitting it the normal way, any idea?
Using #WebMvcTest, the container will inject only components related to Spring MVC (#Controller, #ControllerAdvice, etc.) not the full configuration use #SpringBootTest with #AutoConfigureMockMvc instead.
Spring Boot Javadoc
Keycloak's AutoConfiguration is not included by #WebMvcTest.
You could
Include it manually via #Import(org.keycloak.adapters.springboot.KeycloakSpringBootConfiguration.class)
Or use #SpringBootTest
with spring boot 2.5 i had I had to import KeycloakAutoConfiguration into my test.
#WebMvcTest(value = ApplicationController.class, properties = "spring.profiles.active:test")
#Import(KeycloakAutoConfiguration.class)
public class WebLayerTest {
// ... test code ....
}

How to use the method amazonSQSClient() in the main class?

I am new in Spring Boot. Does anyone know how to call the method amazonSQSClient() in the main class?
#Configuration
public class AWSConfiguration {
#Value("${aws.access.key.encrypted}")
private String amazonAWSAccessKey;
#Value("${aws.secret.key.encrypted}")
private String amazonAWSSecretKey;
#Value("${aws.region.encrypted}")
private String amazonAWSRegion;
#Bean
public AmazonSQS amazonSQSClient() {
AWSCredentials awsCredentials = new BasicAWSCredentials(amazonAWSAccessKey, amazonAWSSecretKey);
AmazonSQS client = AmazonSQSClientBuilder.standard()
.withRegion(amazonAWSRegion)
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials)).build();
return client;
}
}
You shouldn't need to. That's the point of using the #Bean annotation, Spring will inject an instance of AmazonSQS whenever you need one. So, you might have a class like this that uses the SQS client:
#Service
public class QueueService {
private AmazonSQS amazonSQS;
#Autowired
public QueueService(AmazonSQS sqs) {
this.amazonSQS = sqs;
}
}
The Spring framework is smart enough to see that it needs to inject an instance of AmazonSQS, and by marking the method amazonSqsClient with the #Bean annotation, you are telling Spring to use that method whenever it needs to create an instance of AmazonSQS.

Spring Boot mocking other rest clients when testing

I'm consuming a remote api in my Spring MVC project using RestTemplate. One client is for authentication named LoginApiClient and I have to use this in all other rest clients.
This one my rest client that uses LoginApiClient and consumes QrCode Api.
#Service
public class QrKeyApiClient implements QrKeyApiClientBase {
private RestTemplate restTemplate;
#Autowired
private LoginApiClientBase loginApiClient;
public QrKeyApiClient(RestTemplateBuilder builder) {
restTemplate = builder.build();
restTemplate.setErrorHandler(new ErrorHandler());
}
//Other implementation details
}
And this is LoginApiClient
#Service
public class LoginApiClient implements LoginApiClientBase {
private RestTemplate restTemplate;
public LoginApiClient(RestTemplateBuilder builder) {
restTemplate = builder.build();
restTemplate.setErrorHandler(new ErrorHandler());
}
//Other implementation details
}
My unit test class for QrKeyApiClient is like below.
#RunWith(SpringRunner.class)
#RestClientTest({QrKeyApiClient.class})
#Category(IUnitTest.class)
public class QrKeyApiClientTest {
#Value("${getQrCodeUrl}")
private String getQrCodeUrl;
#Mock
LoginApiClient loginApiClient;
#Autowired
private MockRestServiceServer server;
#InjectMocks
private QrKeyApiClient client;
#Test
public void getQrCodeAsImage_makesTrueCallToApi() throws Exception {
ResponseEntity<String> responseEntity = mock(ResponseEntity.class);
HttpEntity requestEntity = new HttpEntity<>(qrCodeGenerateRequest,new HttpHeaders());
this.server
.expect(requestTo(this.getQrCodeUrl))
.andExpect(method(HttpMethod.POST))
.andRespond(withSuccess("successResult", MediaType.TEXT_PLAIN));
String qrImage = this.client.getQrCodeAsImage(qrCodeGenerateRequest);
server.verify();
assertThat(qrImage, is(notNullValue(String.class)));
}
}
I'm not sure this is the right way to do this but I want to mock my LoginApiClient in this test and inject it to QrKeyApiClient. But my test not passes with an error that says "Error creating bean with name 'qrKeyApiClient': Unsatisfied dependency expressed through field 'loginApiClient';"
So how I can test this client with mock of other clients that member of it.
It seems the problem is a mixed usage of Spring and Mockito annotations. Spring provides the #MockBean annotation to mock services in the application context.
Try replacing #Mock with #MockBean and #InjectMocks with #Autowired. The requestEntity and responseEntity are not needed then probably.
#RunWith(SpringRunner.class)
#RestClientTest({QrKeyApiClient.class})
#Category(IUnitTest.class)
public class QrKeyApiClientTest {
#Value("${getQrCodeUrl}")
private String getQrCodeUrl;
#MockBean
LoginApiClient loginApiClient;
#Autowired
private MockRestServiceServer server;
#Autowired
private QrKeyApiClient client;
#Test
public void getQrCodeAsImage_makesTrueCallToApi() throws Exception {
this.server
.expect(requestTo(this.getQrCodeUrl))
.andExpect(method(HttpMethod.POST))
.andRespond(withSuccess("successResult", MediaType.TEXT_PLAIN));
String qrImage = this.client.getQrCodeAsImage(qrCodeGenerateRequest);
server.verify();
assertThat(qrImage, is(notNullValue(String.class)));
}
}

Mocking config with spring boot isn't picking up property files

I am trying to write a IntegrationFlow test. It goes something like this:
JMS(in) -> (find previous versions in db) -> reduce(in,1...n) -> (to db) -> JMS(out)
So, no suprise: I want to mock the DB calls; they are Dao beans. But, I also want it to pickup other beans through component scan; I will selectively scan all packages except dao.
Create a test config and mock the Daos. No problem
Follow spring boot instructions for testing to get Component scanned beans. No problem
I just want to verify the sequence of steps and the resultant output as the outbound JMS queue would see it. Can someone just help me fill in the blanks?
This CANT be tough! The use of mocks seems to be problematic because plenty of essential fields are final. I am reading everywhere about this and just not coming up with a clear path. I inherited this code BTW
My error:
org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers
Here is my code
#Configuration
#ImportResource("classpath:retry-context.xml")
public class LifecycleConfig {
#Autowired
private MessageProducerSupport inbound;
#Autowired
private MessageHandler outbound;
#Autowired
#Qualifier("reducer")
private GenericTransformer<Collection<ExtendedClaim>,ExtendedClaim> reducer;
#Autowired
#Qualifier("claimIdToPojo")
private GenericTransformer<String,ClaimDomain> toPojo;
#Autowired
#Qualifier("findPreviousVersion")
private GenericTransformer<ExtendedClaim,Collection<ExtendedClaim>> previousVersions;
#Autowired
#Qualifier("saveToDb")
private GenericHandler<ExtendedClaim> toDb;
#Bean
public DirectChannel getChannel() {
return new DirectChannel();
}
#Bean
#Autowired
public StandardIntegrationFlow processClaim() {
return IntegrationFlows.from(inbound).
channel(getChannel()).
transform(previousVersions).
transform(reducer).
handle(ExtendedClaim.class,toDb).
transform(toPojo).
handle(outbound).get();
}
}
Test Config
#Configuration
public class TestConfig extends AbstractClsTest {
#Bean(name = "claimIdToPojo")
public ClaimIdToPojo getClaimIdToPojo() {
return spy(new ClaimIdToPojo());
}
#Bean
public ClaimToId getClaimToIdPojo() {
return spy(new ClaimToId());
}
#Bean(name = "findPreviousVersion")
public FindPreviousVersion getFindPreviousVersion() {
return spy(new FindPreviousVersion());
}
#Bean(name = "reducer")
public Reducer getReducer() {
return spy(new Reducer());
}
#Bean(name = "saveToDb")
public SaveToDb getSaveToDb() {
return spy(new SaveToDb());
}
#Bean
public MessageProducerSupport getInbound() {
MessageProducerSupport mock = mock(MessageProducerSupport.class);
// when(mock.isRunning()).thenReturn(true);
return mock;
}
#Bean
public PaymentDAO getPaymentDao() {
return mock(PaymentDAO.class);
}
#Bean
public ClaimDAO getClaimDao() {
return mock(ClaimDAO.class);
}
#Bean
public MessageHandler getOutbound() {
return new CaptureHandler<ExtendedClaim>();
}
}
Actual test won't load
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {TestConfig.class, LifecycleConfig.class})
public class ClaimLifecycleApplicationTest extends AbstractClsTest {
#Autowired
private MessageHandler outbound;
#Autowired
#Qualifier("reducer")
private GenericTransformer<Collection<ExtendedClaim>,ExtendedClaim> reducer;
#Autowired
#Qualifier("claimIdToPojo")
private GenericTransformer<String,ClaimDomain> toPojo;
#Autowired
#Qualifier("findPreviousVersion")
private GenericTransformer<ExtendedClaim,Collection<ExtendedClaim>> previousVersions;
#Autowired
#Qualifier("saveToDb")
private GenericHandler<ExtendedClaim> toDb;
#Autowired
private DirectChannel defaultChannel;
#Test
public void testFlow() throws Exception {
ExtendedClaim claim = getClaim();
Message<ExtendedClaim> message = MessageBuilder.withPayload(claim).build();
List<ExtendedClaim> previousClaims = Arrays.asList(claim);
defaultChannel.send(message);
verify(previousVersions).transform(claim);
verify(reducer).transform(previousClaims);
verify(toDb).handle(claim, anyMap());
verify(toPojo).transform(claim.getSubmitterClaimId());
verify(outbound);
}
}
There are a lot of domain-specific object, so I can't test it to reproduce or find some other issue with your code.
But I see that you don't use an #EnableIntegration on your #Configurations classes.

Categories