SpringBootTest - how to assert if context loading fails - spring-boot

I wrote an ApplicationListener that should check if the environment is prepared during context initialization. I'm having trouble testing the scenario since I'm adding the listener manually both in my configure() and main() methods.
ApplicationListener class:
public class EnvironmentPrepared implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
#Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
//code that checks if conditions are met
if (checkTrue) {
throw new RuntimeException();
}
}
}
Main class:
public class MyApp extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
setRegisterErrorPageFilter(false);
return application.listeners(new EnvironmentPrepared()).sources(MyApp.class);
}
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(MyApp.class);
springApplication.addListeners(new EnvironmentPrepared());
springApplication.run(args);
}
}
The test I want to execute:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ContextConfiguration(loader = OverriddenProfilesTest.CustomLoader.class)
public class OverriddenProfilesTest {
public static class CustomLoader extends SpringBootContextLoader {
#Override
protected SpringApplication getSpringApplication() {
SpringApplication app = super.getSpringApplication();
app.addListeners(new EnvironmentPrepared());
return app;
}
}
/**
* Checks if spring can bootstrap everything
*/
#Test(expected = RuntimeException.class)
public void test() {
}
}
This would be the test I want. A RuntimeException is thrown but the exception happens during context initialization so the test doesn't even start.

Here is the solution I used. I removed the manual adding of the listener to the application and used spring.factories file instead.
Regarding the test, I first created a custom runner class:
public class SpringRunnerWithExpectedExceptionRule extends SpringJUnit4ClassRunner {
public SpringRunnerWithExpectedExceptionRule(Class<?> clazz) throws InitializationError {
super(clazz);
}
#Override
protected Statement methodBlock(FrameworkMethod frameworkMethod) {
List<ExpectedException> testRules = getTestClass().getAnnotatedFieldValues(null, ExpectedExceptionClassRule.class, ExpectedException.class);
Statement result = super.methodBlock(frameworkMethod);
for (TestRule item : testRules) {
result = item.apply(result, getDescription());
}
return result;
}}
Then I create following annotation:
#Retention(RUNTIME)
#Target({ FIELD })
public #interface ExpectedExceptionClassRule {
}
And finally, I was able to run the test with my runner:
#RunWith(SpringRunnerWithExpectedExceptionRule.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class OverriddenProfilesTest {
#ExpectedExceptionClassRule
public static ExpectedException expectedException = ExpectedException.none();
#BeforeClass
public static void before() {
expectedException.expectCause(runtimeExceptionMethod());
}
#Test
public void testThatShouldThrowExceptionWhileSettingContext {
}
static Matcher<Throwable> runtimeExceptionMethod() {
return new IsRuntimeException();
}
static class IsRuntimeException extends TypeSafeMatcher<Throwable> {
//do stuff
}
More on the solution can be found here.

Related

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

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?

How to do integration tests with ClasspathBeanDefinitionScanner in main class springboot

here's the test I created with JPA
import static org.assertj.core.api.Assertions.assertThat;
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#ExtendWith(SpringExtension.class)
#DataJpaTest
public class IntegrationTest {
#Autowired
private JpaUserRepository jpaUser;
#Test
#DisplayName("Test JPA")
void given123Password_whenPasswordIsNotValid_thenIsFalse() {
Boolean teste = this.jpaUser.existsById("Bruno");
assertThat(teste).isEqualTo(true);
}
}
here`s my main class
#SpringBootApplication
public class CleanArchitectureApplication {
public static void main(String[] args) {
SpringApplication.run(CleanArchitectureApplication.class);
}
}
Success case [https://i.stack.imgur.com/APbBu.png][1]
The problem starts when I add a classPathBeanScanner in my main class
#SpringBootApplication
public class CleanArchitectureApplication {
public static void main(String[] args) {
SpringApplication.run(CleanArchitectureApplication.class);
}
#Bean
BeanFactoryPostProcessor beanFactoryPostProcessor(ApplicationContext beanRegistry) {
return beanFactory -> {
genericApplicationContext((BeanDefinitionRegistry) ((AnnotationConfigServletWebServerApplicationContext) beanRegistry).getBeanFactory());
};
}
void genericApplicationContext(BeanDefinitionRegistry beanRegistry) {
ClassPathBeanDefinitionScanner beanDefinitionScanner = new ClassPathBeanDefinitionScanner(beanRegistry);
beanDefinitionScanner.addIncludeFilter(removeModelAndEntitiesFilter());
beanDefinitionScanner.scan("com.baeldung.pattern.cleanarchitecture");
}
static TypeFilter removeModelAndEntitiesFilter() {
return (MetadataReader mr, MetadataReaderFactory mrf) -> !mr.getClassMetadata()
.getClassName()
.endsWith("Model");
}
}
Error case [https://i.stack.imgur.com/IL0Qf.png][2]
I'm trying to implement clean architecture and abstracting main class in spring boot from this article I'm reading[https://www.baeldung.com/spring-boot-clean-architecture](https://www.stackoverflow.com/). but the problem starts when I try to do the integration test and it conflicts contexts and beans after adding the classPathBeanDefinitionScanner.
I've tried setting up different test contexts and different beans.
[1]: https://i.stack.imgur.com/APbBu.png
[2]: https://i.stack.imgur.com/IL0Qf.png

In TestNG #Autowired object is giving null

this is my first class to start the application
#SpringBootApplication
#ComponentScan
public class PosApplication {
public static void main(String[] args) {
SpringApplication.run(PosApplication.class, args);
}
}
This is my second class and bean is defined and throgh testNGRunner method it will call Testme class
#Configuration
public class WebDriverLibrary {
#Bean
public void testNGRunner()
{
TestNG testng=new TestNG();
XmlSuite suite=new XmlSuite();
List<XmlSuite> suites=new ArrayList<XmlSuite>();
suites.add(suite);
List<XmlClass> clazzes=new ArrayList<XmlClass>();
XmlClass clazz=new XmlClass("com.pos.Testme");
clazzes.add(clazz);
XmlTest test=new XmlTest(suite);
test.setClasses(clazzes);
testng.setXmlSuites(suites);
testng.run();
}
}
this is my 3rd class and here i have autowired TestDemo Class and problem in this class. My Testme class run successfully but TestDemo is get autowired dependancy as null.
public class Testme {
#Autowired TestDemo test; // here i am getting null for test
#Test
public void Print() {
System.out.println("Helo my child");
}
#BeforeTest
public void suite() {
System.out.println("MY Suite");
test.launchBrow();
}
#Component
public class TestDemo {
public void launchBrow()
{
System.out.println("Hit the Url");
}
}

Picocli Spring boot CLI Application

I am trying to create a spring boot CLI App using picocli. I followed the steps as mentioned in the tutorial, but when I start the service the whole flow runs.
What I want is to call the command from the terminal then only the flow should trigger.Can anyone please help me resolving this.
Below is my code.
Component class
public class AppCLI implements CommandLineRunner {
#Autowired
AppCLI appCLI;
public String hello(){
return "hello";
}
#CommandLine.Command(name = "command")
public void command() {
System.out.println("Adding some files to the staging area");
}
#Override
public void run(String... args) throws Exception {
CommandLine commandLine = new CommandLine(appCLI);
commandLine.parseWithHandler(new CommandLine.RunLast(), args);
System.out.println("In the main method");
hello();
command();
}
}
Command class
#Controller
#CommandLine.Command(name = "xyz",description = "Performs ", mixinStandardHelpOptions = true, version = "1.0")
public class AppCLI implements Runnable{
#Override
public void run() {
System.out.println("Hello");
}
}
Main Class
#SpringBootApplication
public class Application {
private static Logger LOG = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Thanks in advance.
If you want to add command line parsing to Spring Boot's external
configuration in the #SpringBootApplication, do something like (see
Connect.java):
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine;
#SpringBootApplication
#Command
#NoArgsConstructor #ToString #Log4j2
public class Connect implements ApplicationRunner {
#Option(description = { "connection_file" }, names = { "-f" }, arity = "1")
#Value("${connection-file:#{null}}")
private String connection_file = null;
#Override
public void run(ApplicationArguments arguments) throws Exception {
new CommandLine(this)
.setCaseInsensitiveEnumValuesAllowed(true)
.parseArgs(arguments.getNonOptionArgs().toArray(new String [] { }));
/*
* Command implementation; command completes when this method
* completes.
*/
}
}
There is a similar example in
Install.java.

RequestMapping in Sprint-boot VAADIN application

I have a Spring-boot VAADIN application with main classes as follows
Application Class
#SpringBootApplication
public class MySpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(MySpringBootApplication.class, args);
}
}
VAADIN-UI Class
#Theme("valo")
#SpringUI
public class MyAppUI extends UI {
#Autowired
private SpringViewProvider viewProvider;
#Override
protected void init(VaadinRequest vaadinRequest) {
final VerticalLayout mainLayout = new VerticalLayout();
setContent(mainLayout);
Navigator navigator = new Navigator(this, mainLayout);
navigator.addProvider(viewProvider);
}
}
VAADIN-View Class
#SpringView(name = "")
public class MyAppView extends VerticalLayout implements View {
#PostConstruct
void init() {
// Some logic here
}
#Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
// Some logic here
}
}
Currently, the application handles the request in root URL i.e. say http://localhost:8080/. But I want the application to handle requests when a parameter is supplied by http://localhost:8080/<parameter_value>. How can I achieve this?
The logic I have to execute is the same in both cases i.e. I want MyAppView to process both root URL request and the one with a parameter value.
VAADIN's getUI().getPage().getLocation().getPath() method can be used to get the parameter value from the URL. This will give everything from '/' in the URL. Sample code is given below:
VAADIN-View Class
#SpringView(name = "")
public class MyAppView extends VerticalLayout implements View {
#PostConstruct
void init() {
// Some logic here
}
#Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
// Remove spaces and also initial '/' in the path
String uriParamValue = getUI().getPage().getLocation().getPath().trim().substring(1);
// Do processing with uriParamValue
}
}

Resources