Is it possible to override #DisabledIfEnvironmentVariable in jUnit 5? - java

I have a class of jUnit 5 tests that is not allowed to run in the main pipeline (for multiple reasons). In order to disabled those tests in the pipeline but work on a developer machine I introduced #DisabledIfEnvironmentVariable for the test class (and it works great):
#DisabledIfEnvironmentVariable(named = "USER", matches = "(.*jenkins.*|.*tomcat.*)")
#SpringBootTest(classes = {BigApplication.class}, webEnvironment = RANDOM_PORT)
class LongRunningApplicationTest { ... }
How can I override #DisabledIfEnvironmentVariable if I want to run the test class on an occasion?
I tried adding #EnabledIfEnvironmentVariable hoping that it will override #DisabledIfEnvironmentVariable annotation, therefore providing me with a convenient way to run the test in the pipeline on occasion:
#EnabledIfEnvironmentVariable(named = "applicationTest", matches = "true")
#DisabledIfEnvironmentVariable(named = "USER", matches = "(.*jenkins.*|.*tomcat.*)")
#SpringBootTest(classes = {BigApplication.class}, webEnvironment = RANDOM_PORT)
class LongRunningApplicationTest { ... }
However the above approach doesn't work. Is there a way to override #DisabledIf... ?

One solution is to introduce your own conditions using following annotations:
#EnabledIf or #DisabledIf.
#EnabledIf("EnabledIfAnnotationUtils#shouldRun")
class ApplicationTest {
#Test
void renameMe() {
assertThat(false).isTrue();
}
}
Where EnabledIfAnnotationUtils - is external class (in case you have multiple tests under same condition) and #shouldRun - name of static method. Example:
public class EnabledIfAnnotationUtils {
static boolean shouldRun() {
boolean override = getPropertySafely("run-long-tests").equalsIgnoreCase("true");
if(override) return true;
String user = getEnvSafely("USER");
boolean isOnJenkins = user.toLowerCase().contains("jenkins") || user.toLowerCase().contains("tomcat");
return !isOnJenkins;
}
private static String getPropertySafely(String name) {
return "" + System.getProperty(name);
}
private static String getEnvSafely(String name) {
return "" + System.getenv(name);
}
}
Now tests will NOT run on Jenkins unless override parameter passed, example:
mvn test -Drun-long-tests=true

Related

Java - Constructor Unit testing

I have following Java configuration class which I need to unit test using JUnit:
public class Config {
private static final String AMQ_CONNECTION_URL_TEMPLATE = "failover:(%s)";
private final String awsAmqUrl;
public Config(String url, Optional<String> amqConnectionOptions, PropertiesManager propertiesManager) {
String urlParameter = propertiesManager.getStringParameter(url);
this.awsAmqUrl = constructAmqConnectionString(urlParameter, amqConnectionOptions);
}
private String constructAmqConnectionString(String urlParameter, Optional<String> connectionOptions) {
if (connectionOptions.isPresent()) {
urlParameter = Stream.of(urlParameter.split(","))
.map(url -> url + "?" + connectionOptions.get())
.collect(Collectors.joining(","));
}
return String.format(AMQ_CONNECTION_URL_TEMPLATE, urlParameter);
}
public ConnectionFactory getConnectionFactory() {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(awsAmqUrl);
return connectionFactory;
}
}
I am struggling to find an optimal solution for constructAmqConnectionString method unit testing as it's marked as private.
There are 3 scenarios I am trying to cover:
urlParameter - comprises comma separated URLs (url1,url2),
connectionOptions is not empty;
urlParameter - comprises comma
separated URLs (url1,url2), connectionOptions is empty;
urlParameter - comprises single URL (url1), connectionOptions is
not empty.
Current solution is to add a getter into Config class for awsAmqUrl field so that logic of constructor's call can be verified/tested:
public String getAwsAmqUrl() {
return this.awsAmqUrl;
}
Tests itself have following logic:
#Test
public void verifyConstructorWithoutMqOptionsMultiBroker() {
when(propertiesManager.getStringParameter(any())).thenReturn("url1,url2");
Optional<String> amqConnectionOptions = Optional.empty();
config = new Config("url1,url2", amqConnectionOptions, propertiesManager);
assertEquals(String.format("failover:(url1,url2)"),config.getAwsAmqUrl());
}
#Test
public void verifyConstructorWithMqOptionsMultiBroker() {
when(propertiesManager.getStringParameter(any())).thenReturn("url1,url2");
Optional<String> amqConnectionOptions = Optional.of("optionTest=1");
config = new Config("url1,url2", amqConnectionOptions, propertiesManager);
assertEquals(String.format("failover:(url1?%1$s,url2?%1$s)",amqConnectionOptions.get()),config.getAwsAmqUrl());
}
#Test
public void verifyConstructorWithMqOptionsSingleBroker() {
when(propertiesManager.getStringParameter(any())).thenReturn("url1");
Optional<String> amqConnectionOptions = Optional.of("optionTest=1");
config = new Config("url1", amqConnectionOptions, propertiesManager);
assertEquals(String.format("failover:(url1?%1$s)",amqConnectionOptions.get()),config.getAwsAmqUrl());
}
Adding a getter just for Unit testing purposes doesn't feel the right thing to do as it's breaking encapsulation.
Is there a better way to approach testing in such scenario?
The only place that your class uses awsAmqUrl is in the getConnectionFactory method. So it looks like this is the method you'll have to use, to make sure the value of awsAmqUrl is correct. So instead of having a getter for awsAmqUrl, use something like
String storedUrl = objectUnderTest.getConnectionFactory().getBrokerUrl();
and then you can make assertions on that URL.
Sure, it makes your test dependent on the behaviour of ActiveMQConnectionFactory - but that's OK, since your class is tightly coupled to that particular class anyway.

How to override Quarkus #Retry.delay in a unit test?

I have a method:
#Retry(retryOn = SomeException.class, maxRetries = 5, delay = 180000, maxDuration = 360000)
public void checkIfSomethingIsReady() {
if (something != ready) {
throw new SomeException();
} else {
// do stuff
}
}
I'm trying to do some boundary testing on a method like this without having to wait or retry. Is there a way to override this configuration solely for tests?
Yes you can,
MicroProfile Fault Tolerance also allows configuration using
MicroProfile Config. For example:
com.example.MyService/hello/Retry/delay=5
For the following code example:
#Singleton
public class MyService {
#Retry(maxRetries = 10, delay = 180000, retryOn = IOException.class)
public String hello() {
...
}
}
Therefore it would be packagePath.ClassName/methodName/Retry/delay=yourNumber
For tests, just have a different properties file with a different value.
Official Documentation: https://download.eclipse.org/microprofile/microprofile-fault-tolerance-3.0/microprofile-fault-tolerance-spec-3.0.html#_config_fault_tolerance_parameters
https://smallrye.io/docs/smallrye-fault-tolerance/5.0.0/usage/basic.html#_configuration

How to conditionally run parameterized tests in JUnit5?

Exactly I mean tag #EnabledIfSystemProperty using with #ParameterizedTest tag.
When I use #EnabledIfSystemProperty with #Test, the test method is being disabled and after tests run it is grayed out on the list (as I expect):
#Test
#EnabledIfSystemProperty(named = "env", matches = "test")
public void test() {
System.out.println("Only for TEST env");
}
Whereas I use #EnabledIfSystemProperty with #ParameterizedTest, the test is green on the list after tests run but it is not actually executed:
#ParameterizedTest
#EnabledIfSystemProperty(named = "env", matches = "test")
#ValueSource(strings = {"testData.json", "testData2.json"})
public void test(String s) {
System.out.println("Only for TEST env");
}
I execute tests from IntelliJ IDEA.
I need the test to be grayed out on the list. Any ideas? Thanks...
You could move the parameterized test to a #Nested test and apply the condition to it:
#Nested
#EnabledIfSystemProperty(named = "env", matches = "test")
class Inner {
#ParameterizedTest
#ValueSource(strings = {"testData.json", "testData2.json"})
public void test(String s) {
System.out.println("Only for TEST env: " + s);
}
}

How to run the same SpringBootTests for different applications

I have a SpringBoot multimodule application, something like that:
core
customer1 -> depends on core
customer2 -> depends on core
I want to write integration tests for both, but I don't want to duplicate my core test code. Now I have an abstract class with SpringBootTest(classes = Customer1Application.class) and a lot of test classes, mostly testing the core functionality.
#ContextConfiguration
#SpringBootTest(classes = Customer1Application.class)
#AutoConfigureMockMvc
public abstract class AbstractSpringBootTest
{
#Autowired
protected MockMvc mockMvc;
#Autowired
protected Validator validator;
...
}
I want to check if the changes in Customer2 application break something in core functionality, so I want to run these tests with #SpringBootTest(classes = Customer2Application.class) annotation.
How is it possible to configure the application class in the annotation? Is there a way to run the tests with my other application context without manually changing the annotation or duplicating all the steps?
I don't know if it will work, but I would try removing #SpringBootTest from AbstractSpringBootTest and then defining two test classes as follows:
#SpringBootTest(classes = Customer1Application.class)
class Customer1ApplicationSpringBootTest extends AbstractSpringBootTest {}
#SpringBootTest(classes = Customer2Application.class)
class Customer2ApplicationSpringBootTest extends AbstractSpringBootTest {}
EDIT:
So I dug around Spring Boot sources and came up with this solution.
Essentially to be able to use system property or property file to configure which #SpringBootApplication is supposed to be tested you need to copy the source of class org.springframework.boot.test.context.SpringBootConfigurationFinder to your own test source root and the edit method private Class<?> scanPackage(String source) to look something like this (you do not have to use Lombok of course):
private Class<?> scanPackage(String source) {
while (!source.isEmpty()) {
val components = this.scanner.findCandidateComponents(source);
val testConfig = System.getProperties();
val testConfigFile = "test-config.properties";
val applicationClassConfigKey = "main.application.class";
try {
testConfig.load(this.getClass().getResourceAsStream("/" + testConfigFile));
} catch (IOException e) {
logger.error("Error reading configuration file: {}, using default algorithm", testConfigFile);
}
if (testConfig.containsKey(applicationClassConfigKey)) {
if (!components.isEmpty() && testConfig.containsKey(applicationClassConfigKey) && testConfig.getProperty(applicationClassConfigKey) != null) {
boolean found = false;
val configClassName = testConfig.getProperty(applicationClassConfigKey);
for (BeanDefinition component: components) {
if (configClassName.equals(component.getBeanClassName())) {
found = true;
break;
}
}
Assert.state(found,
() -> "Found multiple #SpringBootConfiguration annotated classes "
+ components + ", none of which are of type " + configClassName);
return ClassUtils.resolveClassName(
configClassName, null);
}
} else {
if (!components.isEmpty()) {
Assert.state(components.size() == 1,
() -> "Found multiple #SpringBootConfiguration annotated classes "
+ components);
return ClassUtils.resolveClassName(
components.iterator().next().getBeanClassName(), null);
}
}
source = getParentPackage(source);
}
return null;
}
Check the link for the entire project.
Did you check?
#SpringBootTest(classes = {Customer1Application.class, Customer2Application.class})

Spring Boot and Mockito verify always true

I have a test in which I want to verify that a method was called with given parameters:
#Autowired
private Client client;
#Autowired
private OtherClient otherClient;
#Test
public void test() {
client.push();
Mockito.verify(otherClient).publishReset(
Mockito.anyString(),
Mockito.argThat(l -> l.size() == 3)
);
}
Problem is that Mockito.verify doesn't fail at all, I can replace l -> l.size() == 3 with any other size match and given test will always pass. How is it even possible for verify to always pass whatever I pass to argThat?
Full example below:
#EnableConfigurationProperties
#TestExecutionListeners(listeners = {
DirtiesContextTestExecutionListener.class,
DirtiesContextBeforeModesTestExecutionListener.class,
ServletTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
MockitoTestExecutionListener.class,
TransactionalTestExecutionListener.class,
WithSecurityContextTestExecutionListener.class
})
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
#EnableAspectJAutoProxy(proxyTargetClass = true)
#ContextConfiguration(
loader = SpringBootContextLoader.class,
classes = {MyApp.class, IntegrationTestContext.class})
#RunWith(SpringRunner.class)
public class FooIT {
#Autowired
private Client client;
#Autowired
private OtherClient otherClient;
#Test
public void test() {
client.push();
Mockito.verify(otherClient).publishReset(
Mockito.anyString(),
Mockito.argThat(l -> l.size() == 3)
);
}
}
And a configuration class:
#Configuration
#MockBeans({
#MockBean(OtherClient.class),
})
public class IntegrationTestContext {
}
Is there something that I'm doing wrong? Is Spring interfering with mockito somehow?
Mockito has problems spying on Spring proxies and final classes/methods. In some cases, this may help:
Mockito.mock(SomeMockableType.class,AdditionalAnswers.delegatesTo(someInstanceThatIsNotMockableOrSpyable));
//
// In your case:
#Test
public void test() {
OtherClient yourSpy = Mockito.mock(OtherClient.class,
AdditionalAnswers.delegatesTo(otherClient));
client.push();
Mockito.verify(yourSpy).publishReset(
Mockito.anyString(),
Mockito.argThat(l -> l.size() == 3)
);
}
This has helped me with a similar problem and was inspired by this Mockito-issue on Github.
Problem was in garbage collector, I created a large list l that container a lot of objects (> 30k with many fields), when I reduced the size to e.g. 100 everything started to work correctly.
So basically: don't force mockito to work with objects that take too much memory (probably some kind of weak reference is used), e.g. reduce the size of lists.

Categories