Priority of quarkus injections in #QuarkusTest - quarkus

I have problem with order in integration tests, because I need to load one class before others, but it's currently random.
Is any possibilities to use something that would say to load it first (during running app context) in Quarkus?

I guess it's not possible to order the execution of the Classes in tests. They execute randomly.
I know for sure that you can order the execution of the test methods inside a class.
That said, I used one strategy in one application where I needed to start a process as the first step of my tests.
Inside my test package, I created one class with ApplicationScoped and created one method with the #Observes StartupEvent StartupEvent parameter.
When I execute mvn test, this method is the first to be executed.
Class with a method to start my initializing process
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
import io.quarkus.runtime.ShutdownEvent;
import io.quarkus.runtime.StartupEvent;
#ApplicationScoped
public class AcceptorStarter {
private static final Logger LOG = LoggerFactory.getLogger(AcceptorStarter.class.getName());
#Inject
AcceptorServer acceptorServer;
// Starts automatically with application
public void onStart(#Observes StartupEvent StartupEvent) {
LOG.info("Starting FIX Acceptor to allow tests");
acceptorServer.init();
}
// Ends automatically with application
public void onStop(#Observes ShutdownEvent shutdownEvent) {
LOG.info("Finishing FIX Acceptor");
acceptorServer.stop();
}
}
Ordering the execution of methods
You can order the execution of the test methods, like this:
import static io.restassured.RestAssured.given;
import javax.inject.Inject;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import io.quarkus.test.junit.QuarkusTest;
#QuarkusTest
#TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class PropertiesRestTest {
#Test
#Order(1)
public void getQuickfixSessionSettings() {
given()
.when()
.get("/properties/quickfix-session-settings")
.then()
.statusCode(200);
}
#Test
#Order(2)
public void putQuickfixSessionSettings() {
given()
.body(appProperties.getQuickfixSessionSettings())
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)
.when().put("/properties/quickfix-session-settings")
.then()
.statusCode(200);
}
#Test
#Order(3)
public void restauraQuickfixSessionSettings() {
given()
.when()
.put("/properties/restaura-quickfix-session-settings-original")
.then()
.statusCode(200);
}
}

Related

Running a quarkus main (command line like) from an AWS lambda handler method

I have a quarkus-camel batch application that needs to run under a lambda in AWS. This is working fine with pure java and spring-boot.
I need to be able to start the Quarkus Application from the AWS lambda handler method.
Running in batch works fine, but under lambda I get the following error:
Caused by: io.quarkus.bootstrap.BootstrapException: Failed to determine the Maven artifact associated with the application /var/task
This is the main java class. I need to know what to do in the handleRequest method to start the Quarkus (CAMEL) application.
package com.example;
import io.quarkus.runtime.annotations.QuarkusMain;
import io.quarkus.runtime.Quarkus;
import io.quarkus.runtime.QuarkusApplication;
import io.quarkus.arc.Arc;
import io.quarkus.runtime.QuarkusApplication;
import org.apache.camel.quarkus.core.CamelRuntime;
import javax.inject.Inject;
import org.apache.camel.CamelContext;
import org.apache.camel.ProducerTemplate;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
#QuarkusMain
public class Main {
private static final Logger logger = LoggerFactory.getLogger(Main.class);
Gson gson = new GsonBuilder().setPrettyPrinting().create();
public static void main(String... args) {
Quarkus.run(CamelApp.class, args);
}
public static class CamelApp implements QuarkusApplication {
#Inject
ProducerTemplate camelProducer;
#Inject
CamelContext camelContext;
#Override
public int run(String... args) throws Exception {
System.out.println("Hello Camel");
CamelRuntime runtime = Arc.container().instance(CamelRuntime.class).get();
runtime.start(args);
camelProducer.sendBody("direct:lambda", "how about this?");
return runtime.waitForExit();
}
}
public Object handleRequest(final Object input, final Context context) {
logger.info("input: {}", gson.toJson(input));
logger.info("context: {}", gson.toJson(context));
Quarkus.run(CamelApp.class);
// CamelRuntime runtime = Arc.container().instance(CamelRuntime.class).get();
// runtime.start(new String[] {"A","B","C"});
// camelProducer.sendBody("direct:lambda", "how about this?");
// runtime.waitForExit();
return input;
}
}

JUnit 5 exlude tagged test methods within test class

#SpringBootTest
#AutoConfigureMockMvc
#ExcludeTags({"no"})
public class MyClassTest {
#Test
public void test1() {
}
#Test
#Tag("no")
public void test2() {
}
...
}
#RunWith(JUnitPlatform.class)
#SelectClasses({MyClassTest.class})
#IncludeTags({"no"})
public class MyClassTestSuiteTest {
}
Having a Spring Boot 2.3.1 project and testing some REST controllers, in a test class some of the test methods are tagged, and shall not be run, when MyClassTest is run. The annotated methods are run in a test suite (with #IncludeTags("no"). JUnit 5.6.2.
With the test suite I'm not sure it #RunWith has to be used for a test suite, or the JUnit 5 #ExtendWith is the right one? In fact, if not necessary, I don't want to mix JUnit 4 and 5, stick to JUnit 5.
Is there a way to configure simply via annotation or similar, to not run the tagged methods when MyClassTest is run? Like #ExcludeTags for test suites, but this does not work on a class like in the example.
Perhaps two test suites can be created, one with #ExludeTags("no"), one with #IncludeTags("no"). But still, how to prevent then that MyClassTest it run at all?
I don't want to create some Run Configuration in a particular IDE. The preferred way would be to use annotations or similar. Perhaps a Maven configuration would also suffice.
Perhaps on test method level execution of the particular test method can be avoided with some criteria evaluation, if the executed test class is MyClassTest, then don't run that test method.
Interesting here is, I cannot replace #RunWith(JUnitPlatform.class) simply with #ExtendWith(JUnitPlatform.class) as there is type incompatibility. Using #ExtendWith(SpringExtension.class) doesn't give me the possibility to run the class (for example with right-click on the class name, no entry to Run/Debug). But #ExtendWith replaces #RunWith in JUnit 5, what extension to use to run the test suite?
Create Execution Condition ExcludeTagsCondition
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.platform.commons.util.AnnotationUtils;
public class ExcludeTagsCondition implements ExecutionCondition {
private static final ConditionEvaluationResult ENABLED_IF_EXCLUDE_TAG_IS_INVALID =
ConditionEvaluationResult.enabled(
"#ExcludeTags does not have a valid tag to exclude, all tests will be run");
private static Set<String> tagsThatMustBeIncluded = new HashSet<>();
public static void setMustIncludeTags(final Set<String> tagsThatMustBeIncluded) {
ExcludeTagsCondition.tagsThatMustBeIncluded = new HashSet<>(tagsThatMustBeIncluded);
}
#Override
public ConditionEvaluationResult evaluateExecutionCondition(
ExtensionContext context) {
final AnnotatedElement element = context
.getElement()
.orElseThrow(IllegalStateException::new);
final Optional<Set<String>> tagsToExclude = AnnotationUtils.findAnnotation(
context.getRequiredTestClass(),
ExcludeTags.class
)
.map(a ->
Arrays.asList(a.value())
.stream()
.filter(t -> !tagsThatMustBeIncluded.contains(t))
.collect(Collectors.toSet())
);
if (!tagsToExclude.isPresent() || tagsToExclude.get().stream()
.allMatch(s -> (s == null) || s.trim().isEmpty())) {
return ENABLED_IF_EXCLUDE_TAG_IS_INVALID;
}
final Optional<String> tag = AnnotationUtils.findAnnotation(element, Tag.class)
.map(Tag::value);
if (tagsToExclude.get().contains(tag.map(String::trim).orElse(""))) {
return ConditionEvaluationResult
.disabled(String.format(
"test method \"%s\" has tag \"%s\" which is on the #ExcludeTags list \"[%s]\", test will be skipped",
(element instanceof Method) ? ((Method) element).getName()
: element.getClass().getSimpleName(),
tag.get(),
tagsToExclude.get().stream().collect(Collectors.joining(","))
));
}
return ConditionEvaluationResult.enabled(
String.format(
"test method \"%s\" has tag \"%s\" which is not on the #ExcludeTags list \"[%s]\", test will be run",
(element instanceof Method) ? ((Method) element).getName()
: element.getClass().getSimpleName(),
tag.orElse("<no tag present>"),
tagsToExclude.get().stream().collect(Collectors.joining(","))
));
}
}
Create annotation #ExcludeTags
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.junit.jupiter.api.extension.ExtendWith;
#Target({ TYPE, ANNOTATION_TYPE })
#Retention(RUNTIME)
#ExtendWith(ExcludeTagsCondition.class)
public #interface ExcludeTags {
String[] value();
}
On your test
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
#ExcludeTags({"foo", "bar"})
#SpringBootTest
class AppTest {
#Test
#Tag("foo")
void test1() {
System.out.println("test1");
}
#Test
#Tag("bar")
void test2() {
System.out.println("test2");
}
#Test
#Tag("baz")
void test3() {
System.out.println("test3");
}
}
When you run the test, you should see the following output:
test method "test1" has tag "foo" which is on the #ExcludeTags list "[bar,foo]", test will be skipped
test method "test2" has tag "bar" which is on the #ExcludeTags list "[bar,foo]", test will be skipped
test3
And your test runner should show 1 test passing and 2 skipped.
Now for your test suite:
Create an annotation #MustIncludeTags
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
#Target({ TYPE, ANNOTATION_TYPE })
#Retention(RUNTIME)
public #interface MustIncludeTags {
String[] value();
}
Now setup your test suite like so:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.runner.RunWith;
#RunWith(JUnitPlatform.class)
#SelectClasses({MyTestSuite.SetupTests.class, AppTest.class})
#MustIncludeTags({"foo", "bar"})
public class MyTestSuite {
public static class SetupTests {
#BeforeAll
public static void beforeClass() {
ExcludeTagsCondition.setMustIncludeTags(
Optional.ofNullable(MyTestSuite.class.getAnnotation(MustIncludeTags.class))
.map(MustIncludeTags::value)
.map(Arrays::asList)
.orElse(new ArrayList<>())
.stream()
.collect(Collectors.toSet())
);
}
#Disabled
#Test
void testDummy() {
// this test needs to be present for the beforeAll to run
}
}
}
When you run your test suite with the #MustIncludeTags the #ExcludedTags are overridden.
As you can see from the following test execution:

How to assert that the controller has been created in Spring Boot?

According to the tutorial Testing the Web Layer, testing that the controller has been created can be done with the following code:
#Test
public void contexLoads() throws Exception {
assertThat(controller).isNotNull();
}
but I get the following error:
The method assertThat(T, Matcher<? super T>) in the type Assert is not applicable for the arguments (HomeController)"
even with the statement:
import static org.junit.Assert.assertThat;
The code of my class is the same than the one given in the example:
package com.my_org.my_app;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
#RunWith(SpringRunner.class)
#SpringBootTest
public class SmokeTest {
#Autowired
private HomeController controller;
#Test
public void contexLoads() throws Exception {
assertThat(controller).isNotNull();
}
}
If I change the assert statement to:
#Test
public void contexLoads() throws Exception {
assertNotNull(controller);
}
it works as expected.
My controller class has some Autowired objects, but since they are managed by Spring Boot it should not be an issue. Any idea of what could be wrong with assertThat(controller).isNotNull();? Thanks in advance.
You used the wrong assertThat import. You should use the following:
import static org.assertj.core.api.Assertions.assertThat;
The correct method is located in AssertJ library, not in JUnit.

How to make Spring IoC container available through out project

I feel stupid to even ask for this but I spent days looking for the answer and I'm still with nothing.
I wanna include simple Spring IoC container in my project. All I want it to do is to allow me Injecting/Autowiring some reusable objects in other classes. What I've done so far looks like this:
-> Project structure here <-
Configuration code:
package com.example;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import java.util.Random;
#Configuration
#ComponentScan(basePackages = "com.example")
public class AppConfig {
#Bean
public Random rand() {
return new Random(42);
}
#Bean
public String string() {
return "Hello World!";
}
}
Main class code:
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Random;
public class Main {
#Autowired
Random rand;
#Autowired
String string;
public static void main(String[] args) {
// workflow
Main main = new Main();
System.out.println(main.string);
}
}
AnotherClass code:
package com.example.deeperpackage;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Random;
public class AnotherClass {
#Autowired
Random rand;
#Autowired
String string;
public void methodToBeCalled() {
// TODO
System.out.println(string);
}
}
How can I make these #Autowired annotations work? Do I have to instantiate container in every single class in which I want to autowire components? I've seen in work a oracle app which used Spring and #Inject to distribute objects to numerous classes and there was no container logic in any class available for me. Just fields with #Inject annotation. How to achieve that?
Simply add the annotation #Component on the classes you want to inject :
#Component
public class AnotherClass {
...
}
But you cannot inject static attributes and when you do new Main(), no Spring context is being created. If you use Spring Boot, you should look at how to write a main with it.
https://spring.io/guides/gs/spring-boot/

Spring aop doesn't run when project starts

I'v implemented a spring-boot aop demo and it runs well, but when I want to use it to load some resource when the project starts, it doesn't work somehow
Aop:
package com.neo.mysql;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* Created by li_weia on 2017/7/6.
*/
#Aspect
#Component
public class DynamicDataSourceAspect {
#Before("#annotation(VendorSource)")
public void beforeSwitchDS(JoinPoint point){
//获得当前访问的class
Class<?> className = point.getTarget().getClass();
//获得访问的方法名
String methodName = point.getSignature().getName();
//得到方法的参数的类型
Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
String dataSource = DataSourceContextHolder.DEFAULT_DS;
try {
// 得到访问的方法对象
Method method = className.getMethod(methodName, argClass);
// 判断是否存在#DS注解
if (method.isAnnotationPresent(VendorSource.class)) {
VendorSource annotation = method.getAnnotation(VendorSource.class);
// 取出注解中的数据源名
dataSource = annotation.value();
}
} catch (Exception e) {
e.printStackTrace();
}
// 切换数据源
DataSourceContextHolder.setDB(dataSource);
}
#After("#annotation(VendorSource)")
public void afterSwitchDS(JoinPoint point){
DataSourceContextHolder.clearDB();
}
}
The VendorSource annotation:
package com.neo.mysql;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by li_weia on 2017/7/6.
*/
#Target({ ElementType.METHOD, ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
public #interface VendorSource {
String value() default "vendor-master";
}
It runs well here, I can successfully change datasource by annotation:
package com.neo.web;
import com.neo.entity.SiteEntity;
import com.neo.mapper.ClassMappingDao;
import com.neo.mysql.VendorSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
#RestController
public class UserController {
private final ClassMappingDao siteMapper;
#Autowired(required = false)
public UserController(ClassMappingDao siteMapper) {
this.siteMapper = siteMapper;
}
#RequestMapping("/getSites")
#VendorSource("vendor-read")
public List<SiteEntity> getUsers() {
return siteMapper.getAllSite();
}
}
but it doesn't work here, the aop method is not invoked at all:
package com.neo.component;
import com.neo.entity.SiteEntity;
import com.neo.mapper.ClassMappingDao;
import com.neo.mysql.VendorSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* Created by li_weia on 2017/7/7.
*/
#Component
public class TestComponent{
private final ClassMappingDao userMapper;
#Autowired(required = false)
public TestComponent(ClassMappingDao userMapper) {
this.userMapper = userMapper;
init();
}
#VendorSource("vendor-read")
public void init() {
List<SiteEntity> sites = userMapper.getAllSite();
for(SiteEntity site: sites){
System.out.println(site.getSite());
}
}
}
You need to fully qualify the annotation, like so:
#Before("execution(public * *(..)) && #annotation(com.neo.mysql.VendorSource)")
private void whatever() {}
Also, as mentioned in my comment above, you need to have spring-boot-starter-aop on classpath. Maybe you already do, but since you didn't say, it's worth mentioning.
Edit:
I didn't notice the real problem before, I wasn't paying attention.
Spring AOP only triggers if you make calls from another class. This is because Spring needs to be able to intercept the call and run the pointcut. Calling the method from constructor is not going to do anything.
You can do a hackish workaround. Create a #PostConstruct void postConstruct() {} method in your class (not constructor), autowire ApplicationContext, and then do MyClassWithInitMethod myClass = context.getBean(MyClassWithInitMethod.class) in the postConstruct method. Then call the method on myClass, and AOP will kick in.
Frankly, I didn't previously check what you are doing in your pointcut, and it's a terrible idea. When multiple threads run, they are going to overwrite the static context, and create a race-condition that you'll then create another question for. Don't do it! Use the factory pattern instead, and inject the DataSourceFactory in the classes that now have the annotation.

Resources