How to test a try...finally method only been called once in SpringBoot? - spring-boot

I am following this article to implement a database read/write separation feature by calling different methods. However, I got the error:
Missing method call for verify(mock) here: verify(spyDatabaseContextHolder, times(1)).set(DatabaseEnvironment.READONLY);
when doing the testing.
My test case is trying to verify DatabaseEnvironment.READONLY has been set once when using TransactionReadonlyAspect AOP annotation:
// TransactionReadonlyAspectTest.java
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {LoadServiceImpl.class, TransactionReadonlyAspect.class})
public class TransactionReadonlyAspectTest {
#Autowired
private TransactionReadonlyAspect transactionReadonlyAspect;
#MockBean
private LoadServiceImpl loadService;
#Test
public void testReadOnlyTransaction() throws Throwable {
ProceedingJoinPoint mockProceedingJoinPoint = mock(ProceedingJoinPoint.class);
Transactional mockTransactional = mock(Transactional.class);
DatabaseContextHolder spyDatabaseContextHolder = mock(DatabaseContextHolder.class);
when(mockTransactional.readOnly()).thenReturn(true);
when(loadService.findById(16)).thenReturn(null);
when(mockProceedingJoinPoint.proceed()).thenAnswer(invocation -> loadService.findById(16));
transactionReadonlyAspect.proceed(mockProceedingJoinPoint, mockTransactional);
verify(spyDatabaseContextHolder, times(1)).set(DatabaseEnvironment.READONLY); // got the error: Missing method call for verify(mock)
verify(loadService, times(1)).findById(16);
assertEquals(DatabaseContextHolder.getEnvironment(), DatabaseEnvironment.UPDATABLE);
}
}
//TransactionReadonlyAspect.java
#Aspect
#Component
#Order(0)
#Slf4j
public class TransactionReadonlyAspect {
#Around("#annotation(transactional)")
public Object proceed(ProceedingJoinPoint proceedingJoinPoint,
org.springframework.transaction.annotation.Transactional transactional) throws Throwable {
try {
if (transactional.readOnly()) {
log.info("Inside method " + proceedingJoinPoint.getSignature());
DatabaseContextHolder.set(DatabaseEnvironment.READONLY);
}
return proceedingJoinPoint.proceed();
} finally {
DatabaseContextHolder.reset();
}
}
}
// DatabaseContextHolder.java
public class DatabaseContextHolder {
private static final ThreadLocal<DatabaseEnvironment> CONTEXT = new ThreadLocal<>();
public static void set(DatabaseEnvironment databaseEnvironment) {
CONTEXT.set(databaseEnvironment);
}
public static DatabaseEnvironment getEnvironment() {
DatabaseEnvironment context = CONTEXT.get();
System.out.println("context: " + context);
return CONTEXT.get();
}
public static void reset() {
CONTEXT.set(DatabaseEnvironment.UPDATABLE);
}
}
//DatabaseEnvironment.java
public enum DatabaseEnvironment {
UPDATABLE,READONLY
}
// LoadServiceImpl.java
#Service
public class LoadServiceImpl implements LoadService {
#Override
#Transactional(readOnly = true)
public LoadEntity findById(Integer Id) {
return this.loadDAO.findById(Id);
}
...
}
I just want to test DatabaseContextHolder.set(DatabaseEnvironment.READONLY) has been used once then in the TransactionReadonlyAspect finally block it will be reset to DatabaseEnvironment.UPDATABLE which make sense.
However, how to test DatabaseContextHolder.set(DatabaseEnvironment.READONLY) gets called once? Why does this error occur? Is there a better way to test TransactionReadonlyAspect?

Related

How to use Conditional interface in spring boot with value inside a list

I have application-sample.YAML file where I have data. After loading the data, based on certain fields. I want to decide which few components to load or not to load. I can see this below condition class loads first, as a result, I am getting the data null because this condition class is loading first before my Data loaded.
#Configuration
public class AwsCondition implements Condition {
#Autowired
MyTestData data;
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// TODO Auto-generated method stub
log.info("inside...........condition");
if(data.getListeners().size()>1){
return true;
}
return false;
}
}
MyTestData.java
#Slf4j
#Data
#ConfigurationProperties
#Component
public class MyTestData implements InitializingBean {
List<Listener> listeners = new ArrayList<>();
#Data
public static class Listener {
private String type;
private String name;
}
#Override
public void afterPropertiesSet() throws Exception {
if (this.getListeners().isEmpty()) {
log.info("Nothing configured. Please verify application-test.yml is configured properly.");
System.exit(1);
}
}
}
ConditionTest class
#Component
#Conditional(AwsCondition.class)
public class TestS3ListenerRouter extends RouteBuilder {
//My all logic lying here
}
Try the following:
#Component
public class AwsCondition implements Condition {
#Autowired
MyTestData data;
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// TODO Auto-generated method stub
log.info("inside...........condition");
if(data.getListeners().size()>1){
return true;
}
return false;
}
}

PowerMock whenNew Problem On Spring Component Constructor

I have a Spring Service like below:
#Service
public class SendWithUsService
{
private SendWithUs mailAPI;
public SendWithUsService()
{
this.mailAPI = new SendWithUs();
}
public void sendEmailEvent(Dto data)
{
try
{
SendWithUsSendRequest request = new SendWithUsSendRequest()...;
mailAPI.send(request);
}
catch (Exception e)
{
...
}
}
}
And my test code look like below:
#RunWith(PowerMockRunner.class)
#PowerMockIgnore({"javax.net.ssl.*"})
#PrepareForTest(SendWithUsService.class)
public class SendWithUsServiceTest
{
#InjectMocks
private SendWithUsService sendWithUsService;
#Mock
private SendWithUs mailAPI;
#Test
public void sendEmailEvent_successfully() throws Exception
{
whenNew(SendWithUs.class).withAnyArguments().thenReturn(mailAPI);
Dto emailData = ...;
sendWithUsService.sendEmailEvent(emailData);
...
}
}
In here, PowerMock whenNew method doesn't work. But when I call it outside of constructor like inside the sendEmailEvent method, it is triggered.
Is there a way to handle it?
Works:
public void sendEmailEvent(Dto data)
{
this.mailAPI = new SendWithUs();
...
}
Not works:
public SendWithUsService()
{
this.mailAPI = new SendWithUs();
}
I've solved it like below:
#RunWith(PowerMockRunner.class)
#PowerMockIgnore({"javax.net.ssl.*"})
#PrepareForTest(SendWithUsService.class)
public class SendWithUsServiceTest
{
#InjectMocks
private SendWithUsService sendWithUsService;
#Mock
private SendWithUs mailAPI;
#Before
public void setUp() throws Exception {
whenNew(SendWithUs.class).withAnyArguments().thenReturn(mailAPI);
MockitoAnnotations.initMocks(this);
}
#Test
public void sendEmailEvent_successfully() throws Exception
{
Dto emailData = ...;
sendWithUsService.sendEmailEvent(emailData);
...
}
}

SpringBootTest not loading applicationcontext when overriding its value dynamically

I ma using #SpringBootTest for my IT and what I want to do is load a new ApplicationContext for every IT. For this I am overriding the SpringBootTest annotation dynamically and I am providing the same value for classes. But its throwing an exception saying could not load ApplicationContext. Is there an issue if we override the impl for SpringBootTest?
#RunWith(SpringRunner.class)
#ActiveProfiles("test")
#Slf4j
public abstract class OrchestratorBootIT extends BaseIT {
private static final String ANNOTATIONS = "annotations";
public static final String ANNOTATION_DATA = "annotationData";
public static Class<? extends OrchestratorBootIT> runningTestClassType;
public OrchestratorBootIT() {
if(runningTestClassType != null) return;
StepsConfigDAOCacheIT.class.toString());
try {
SpringBootTest annotation = StepsConfigDAOCacheIT.class.getAnnotation(SpringBootTest.class);
//In JDK8 Class has a private method called annotationData().
//We first need to invoke it to obtain a reference to AnnotationData class which is a private class
Method method = Class.class.getDeclaredMethod(ANNOTATION_DATA, null);
method.setAccessible(true);
//Since AnnotationData is a private class we cannot create a direct reference to it. We will have to
//manage with just Object
Object annotationData = method.invoke(StepsConfigDAOCacheIT.class);
//We now look for the map called "annotations" within AnnotationData object.
Field annotations = annotationData.getClass().getDeclaredField(ANNOTATIONS);
annotations.setAccessible(true);
Map<Class<? extends Annotation>, Annotation> map =
(Map<Class<? extends Annotation>, Annotation>) annotations.get(annotationData);
map.put(SpringBootTest.class, new SpringBootTestImpl());
SpringBootTest annotation1 = StepsConfigDAOCacheIT.class.getAnnotation(SpringBootTest.class);
log.info("test");
} catch (Exception e) {
e.printStackTrace();
}
}
#BeforeClass
public static void beforeClass() {
BaseIT.beforeClass();
}
#AfterClass
public static void afterClass() {
runningTestClassType = null;
}
#After
public void cleanup() {
super.cleanup();
}
private boolean setDynamicSpringBootTestAnnotation(final Object annotationData) throws Exception {
Field annotations = annotationData.getClass().getDeclaredField("annotations");
annotations.setAccessible(true);
Map<Class<? extends Annotation>, Annotation> map = (Map<Class<? extends Annotation>, Annotation>) annotations.get(annotationData);
map.put(SpringBootTest.class, getSpringBootTest());
return true;
}
protected SpringBootTest getSpringBootTest() {
return new SpringBootTestImpl();
}
static class SpringBootTestImpl implements SpringBootTest {
#Override
public String[] value() {
return new String[0];
}
#Override
public String[] properties() {
return new String[0];
}
#Override
public Class<?>[] classes() {
return new Class[] {CacheConfig.class};
}
#Override
public WebEnvironment webEnvironment() {
return WebEnvironment.MOCK;
}
#Override
public Class<? extends Annotation> annotationType() {
return SpringBootTestImpl.class;
}
}

Bean not getting overridden in Spring boot

I am trying to write and test an application that used spring-cloud with azure functions following this tutorial.
https://github.com/markusgulden/aws-tutorials/tree/master/spring-cloud-function/spring-cloud-function-azure/src/main/java/de/margul/awstutorials/springcloudfunction/azure
I am tryign to write a testcase and override the bean.
Here is the application class having function and handler Bean function.
#SpringBootApplication
#ComponentScan(basePackages = { "com.package" })
public class DataFunctions extends AzureSpringBootRequestHandler<GenericMessage<Optional<String>>, Data> {
#FunctionName("addData")
public HttpResponseMessage addDataRun(
#HttpTrigger(name = "add", methods = {
HttpMethod.POST }, authLevel = AuthorizationLevel.FUNCTION) HttpRequestMessage<Optional<String>> request,
final ExecutionContext context) throws JsonParseException, JsonMappingException, IOException {
context.getLogger().info("Java HTTP trigger processed a POST request.");
try {
handleRequest(new GenericMessage<Optional<String>>(request.getBody()), context);
} catch (ServiceException ex) {
ErrorMessage em = new ErrorMessage();
return request.createResponseBuilder(handleException(ex, em)).body(em).build();
}
return request.createResponseBuilder(HttpStatus.CREATED).build();
}
#Autowired
MyService mService;
#Bean
public Consumer<GenericMessage<Optional<String>>> addData() {
ObjectMapper mapper = new ObjectMapper();
return req -> {
SomeModel fp = null;
try {
fp = mapper.readValue(req.getPayload().get(), SomeModel.class);
} catch (Exception e) {
throw new ServiceException(e);
}
mService.addData(fp);
};
}
}
I want to test by overriding the above bean.
Cosmosdb spring configuration
#Configuration
#EnableDocumentDbRepositories
public class CosmosDBConfig extends AbstractDocumentDbConfiguration {
#Value("${cosmosdb.collection.endpoint}")
private String uri;
#Value("${cosmosdb.collection.key}")
private String key;
#Value("${cosmosdb.collection.dbname}")
private String dbName;
#Value("${cosmosdb.connect.directly}")
private Boolean connectDirectly;
#Override
public DocumentDBConfig getConfig() {
ConnectionPolicy cp = ConnectionPolicy.GetDefault();
if (connectDirectly) {
cp.setConnectionMode(ConnectionMode.DirectHttps);
} else {
cp.setConnectionMode(ConnectionMode.Gateway);
}
return DocumentDBConfig.builder(uri, key, dbName).connectionPolicy(cp).build();
}
}
Here is the configuration
#TestConfiguration
#PropertySource(value = "classpath:application.properties", encoding = "UTF-8")
#Profile("test")
#Import({DataFunctions.class})
public class TestConfig {
#Bean(name="addData")
#Primary
public Consumer<GenericMessage<Optional<String>>> addData() {
return req -> {
System.out.println("data mock");
};
}
#Bean
#Primary
public DocumentDBConfig getConfig() {
return Mockito.mock(DocumentDBConfig.class);
}
}
Finally the test class
#RunWith(SpringRunner.class)
//#SpringBootTest //Enabling this gives initialization error.
#ActiveProfiles("test")
public class TempTest {
#InjectMocks
DataFunctions func;
#Mock
MyService mService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
private Optional<String> createRequestString(final String res) throws IOException {
InputStream iStream = TempTest.class.getResourceAsStream(res);
String charset="UTF-8";
try (BufferedReader br = new BufferedReader(new InputStreamReader(iStream, charset))) {
return Optional.of(br.lines().collect(Collectors.joining(System.lineSeparator())));
}
}
#Test
public void testHttpPostTriggerJava() throws Exception {
#SuppressWarnings("unchecked")
final HttpRequestMessage<Optional<String>> req = mock(HttpRequestMessage.class);
final Optional<String> queryBody = createRequestString("/test-data.json");
doNothing().when(mService).addData(Mockito.any(SomeModel.class));
doReturn(queryBody).when(req).getBody();
doAnswer(new Answer<HttpResponseMessage.Builder>() {
#Override
public HttpResponseMessage.Builder answer(InvocationOnMock invocation) {
HttpStatus status = (HttpStatus) invocation.getArguments()[0];
return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status);
}
}).when(req).createResponseBuilder(any(HttpStatus.class));
final ExecutionContext context = mock(ExecutionContext.class);
doReturn(Logger.getGlobal()).when(context).getLogger();
doReturn("addData").when(context).getFunctionName();
// Invoke
final HttpResponseMessage ret = func.addDataRun(req, context);
// Verify
assertEquals(ret.getStatus(), HttpStatus.CREATED);
}
}
For this case instead of test configuration addData the actual bean is called from DataFunctions class. Also the database connection is also created when it should use the mocked bean from my test configuration. Can somebody please point out what is wrong in my test configuration?
I was able to resolve the first part of cosmos db config loading by marking it with
#Configuration
#EnableDocumentDbRepositories
#Profile("!test")
public class CosmosDBConfig extends AbstractDocumentDbConfiguration {
...
}
Also had to mark the repository bean as optional in the service.
public class MyService {
#Autowired(required = false)
private MyRepository myRepo;
}
Didn't use any spring boot configuration other than this.
#ActiveProfiles("test")
public class FunctionTest {
...
}
For the second part of providing mock version of Mock handlers, I simply made the test config file as spring application as below.
#SpringBootApplication
#ComponentScan(basePackages = { "com.boeing.da.helix.utm.traffic" })
#Profile("test")
public class TestConfiguration {
public static void main(final String[] args) {
SpringApplication.run(TestConfiguration.class, args);
}
#Bean(name="addData")
#Primary
public Consumer<GenericMessage<Optional<String>>> addData() {
return req -> {
System.out.println("data mock");
};
}
}
and made use of this constructor from azure functions library in spring cloud in my constructor
public class AppFunctions
extends AzureSpringBootRequestHandler<GenericMessage<Optional<String>>, List<Data>> {
public AppFunctions(Class<?> configurationClass) {
super(configurationClass);
}
}
public AzureSpringBootRequestHandler(Class<?> configurationClass) {
super(configurationClass);
}
Hope it helps someone.

Get parent bean in prototype bean that gets injected

I would like to have a Bean and a SubBean like this:
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
#Component
public class SubBean implements ApplicationContextAware{
private Object parent;
public void setApplicationContext(ApplicationContext ctx){
this.parent = doSomeMagicToGetMyParent(ctx);
}
public Object getParent(){
return parent;
}
}
#Component
public class SomeBean implements InitializingBean{
#Resource
private SubBean sub;
public void afterPropertiesSet(){
Assert.isTrue(this == sub.getParent());
}
}
The trick I want to achieve is, that the SubBean automagically gets a reference to the Bean it got injected into. Because the scope of the subbean is prototype, it will get injected as a new instance in every parent that wants it to get injected.
My big idea is to exploit this pattern to write a LoggerBean which can be injected into normal beans. The subbean should work just like a SLF4J Logger.
So does anyone know the magic to make this work? :)
EDIT: I've found a solution to do this with a custom BeanPostProcessor:
#Component
public class DependencyInjectionAwareBeanPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
for (Field f : bean.getClass().getFields()) {
if (f.getType().isInstance(IDependencyInjectionAware.class)) {
ReflectionUtils.makeAccessible(f);
try {
IDependencyInjectionAware diAware = (IDependencyInjectionAware) f.get(bean);
diAware.injectedInto(bean);
} catch (IllegalArgumentException e) {
ReflectionUtils.handleReflectionException(e);
} catch (IllegalAccessException e) {
ReflectionUtils.handleReflectionException(e);
}
}
}
return bean;
}
}
Here is the Interface:
public interface IDependencyInjectionAware {
void injectedInto(Object parent);
}
And here a Bean using it:
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
#Component
public class SomeAwareBean implements IDependencyInjectionAware {
private Object parent;
public void injectedInto(Object parent){
this.parent = parent;
}
public Object getParent(){
return parent;
}
}
Here a test with a normal Bean which works perfectly:
#Component
public class UsingBean implements InitializingBean {
#Resource
private SomeAwareBean b;
public void afterPropertiesSet(){
Assert.notNull(b); //works
Assert.isTrue(b.getParent() == this); //works
}
}
Though, when using the same with a normal class which gets the depedencies injected via #Configurable, the test fails:
#Configurable
public class UsingPlainClass implements InitializingBean {
#Resource
private SomeAwareBean b;
public void afterPropertiesSet(){
Assert.notNull(b); //works
Assert.isTrue(b.getParent() == this); //fails because null is returned
}
}
So this seems to have gotten me to another question: Why won't my custom BeanPostProcessor run on a #Configurable classes? Maybe I have to resort to AspectJ afterall...
EDIT: Just to update the status. I did not implement this afterall because this is overengineering...
I find this simpler:
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
#Component
public class SubBean implements ApplicationContextAware{
private Object parent;
public void setApplicationContext(ApplicationContext ctx){
...
}
public Object getParent(){
return parent;
}
//ADDED CODE
public void setParent(Object parent) {
this.parent = parent;
}
//END ADDED CODE
}
#Component
public class SomeBean implements InitializingBean{
private SubBean sub;
//ADDED CODE
#Resource
public void setSub(SubBean sub) {
this.sub = sub;
sub.setParent(this);
}
//END ADDED CODE
public void afterPropertiesSet(){
Assert.isTrue(this == sub.getParent());
}
}
Fixed several bugs with the solution given by the original poster:
import java.lang.reflect.Field;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.util.ReflectionUtils;
public interface DependencyInjectionAware {
void injectedInto(final Object bean, final String beanName);
public static class DependencyInjectionAwareBeanPostProcessor implements
BeanPostProcessor {
private static final Logger logger = Logger.getLogger(DependencyInjectionAwareBeanPostProcessor.class);
#Override
public Object postProcessBeforeInitialization(final Object bean,
final String beanName) {
return bean;
}
#Override
public Object postProcessAfterInitialization(final Object bean,
final String beanName) {
for (final Field f : bean.getClass().getDeclaredFields()) {
logger.info("scanning field " + f.getName() + " of bean " + beanName + " (class= " + bean.getClass() + ")");
if (DependencyInjectionAware.class.isAssignableFrom(f.getType())) {
ReflectionUtils.makeAccessible(f);
try {
final DependencyInjectionAware diAware = (DependencyInjectionAware) f.get(bean);
diAware.injectedInto(bean, beanName);
} catch (final IllegalArgumentException e) {
ReflectionUtils.handleReflectionException(e);
} catch (final IllegalAccessException e) {
ReflectionUtils.handleReflectionException(e);
}
}
}
return bean;
}
}
}

Resources