Active profile in SpringBootTest based on system variable - spring-boot

As the host of Redis is different in local and CI, my #Tests can pass locally, they can't pass in CI.
Firstly, I tried to mock the RedisTemplate like this:
RedisTemplate redisTemplate = mock(RedisTemplate.class);
ValueOperations valueOperations = mock(ValueOperations.class);
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
when(valueOperations.increment(anyString(), anyLong())).thenReturn(1L);
when(valueOperations.get("a#a.com")).thenReturn("1");
It did mocked RedisTemplate, but can't mock redisTemplate.opsForValue() and valueOperations.increment(...) ( I can't find reason )
Then, I wrote two profiles named application-ci-test.yml and applicaiton-test.yml, tried to active one of them based on system environment variable
I learnd from here that I can set active profile in this way:
#Configuration
public class MyWebApplicationInitializer
implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.setInitParameter(
"spring.profiles.active", "dev");
}
}
and this way:
#Autowired
private ConfigurableEnvironment env;
...
env.setActiveProfiles("someProfile");
But I don't know how to use them. The system variable can get by System.getenv(..). So now I want to know how to active profile based on the variable I get.

I found a way to active corresponding profile based on system variable or property:
import org.springframework.test.context.ActiveProfilesResolver;
public class SpringActiveProfileResolver implements ActiveProfilesResolver {
#Override
public String[] resolve(Class<?> testClass) {
final String isCITest = System.getEnv("isCITest");
return new String[] { isCITest == null ? "test" : "ci-test" };
}
}
then use the parameter resolver in #ActiveProiles:
#ActiveProfiles(resolver = SpringActiveProfileResolver.class)
How to set environment variable is anther issue, and answers above have already answered it

Assuming your #Test methods are in a class with the #SpringBootTest annotation, you can use #ActiveProfiles to set the profile.
#SpringBootTest
#ActiveProfiles("someProfile")

Use run parameters inside your CI job/script.
Depending how You start Your tests, You can for example do it with VM arguments
mvn test -Dspring.profiles.active=ci-test
or
java -jar -Dspring.profiles.active=ci-test
or whatever.
On the other hand you can use program arguments:
java -jar --spring.profiles.active=ci-test
One way or the other, providing active profile at start will activate property file of your choice.
If you want some specific piece of code (configuration class for example) to be run with specific profile only, annotate that piece of code with #Profile("ci-test")
Example:
#Configuration
#Profile("ci-test")
public class SomeConfiguration {
//any configuration beans etc.
}
Following class will only be loaded when Your active profile will be "ci-test". So if You run Your app on Your CI server with one of the commands above, both property file named "ci-test" and this configuration class will get loaded.
It's also worth adding that in order for some code to run in ALL profiles EXCEPT specified, you can negate the name inside profile annotation like: #Profile("!ci-test").
Code annotated like that will run with all profiles (including default) except "ci-test".

Related

how to use org.springframework.format.Formatter.print()

#Configuration
public class MyWebMvcConfigurationSupport extends WebMvcConfigurationSupport {
#Override
public FormattingConversionService mvcConversionService() {
FormattingConversionService f = super.mvcConversionService();
f.addFormatter(new DateFormatter("yyyy-MM-dd"));
return f;
}
}
#RestController
public class TestController {
#GetMapping
public Date test(Date date) {
return date;
}
}
When we access http://localhost:8080?date=2021-09-04, the argument type is converted through the DateFormatter's parse method, which relies on the SpringMVC framework to do the conversion. I wonder if the print method can also be invoked through the framework to return a string.
Do we need to manually invoke the print method, for example
#RestController
public class TestController {
#Resource
private FormattingConversionService conversionService;
#GetMapping
public String test(Date date) {
return conversionService.convert(date, String.class);
}
}
Inside the controller
You could use a class extending java.text.Format like SimpleDateFormatin your controller:
#RestController
public class TestController {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
#GetMapping
public String test(Date date) {
return dateFormat.format(date);
}
}
At application level
Use DateTimeFormatterRegistrar to register your formats, like described in this tutorial.
Then you can register this set of formatters at Spring's FormattingConversionService.
Using Jackson
However if you would like to work with JSON or XML you should consider using FasterXML's Jackson. See similar question:
Spring 3.2 Date time format
This is the interface representing the environment in which the current application is running. It models two key aspects of the application environment: profiles and properties. The methods related to property access are exposed via the PropertyResolver superinterface.
A profile is a named, logical group of bean definitions to be registered with the container only if the given profile is active. Beans may be assigned to a profile whether defined in XML or via annotations; see the spring-beans 3.1 schema or the #Profile annotation for syntax details. The role of the Environment object with relation to profiles is in determining which profiles (if any) are currently active, and which profiles (if any) should be active by default.
Properties play an important role in almost all applications, and may originate from a variety of sources: properties files, JVM system properties, system environment variables, JNDI, servlet context parameters, ad-hoc Properties objects, Maps, and so on. The role of the environment object with relation to properties is to provide the user with a convenient service interface for configuring property sources and resolving properties from them.
Beans managed within an ApplicationContext may register to be EnvironmentAware or #Inject the Environment in order to query profile state or resolve properties directly.
In most cases, however, application-level beans should not need to interact with the Environment directly but instead may have to have ${...} property values replaced by a property placeholder configurer such as PropertySourcesPlaceholderConfigurer, which itself is EnvironmentAware and as of Spring 3.1 is registered by default when using context:property-placeholder/.
Configuration of the environment object must be done through the ConfigurableEnvironment interface, returned from all AbstractApplicationContext subclass getEnvironment() methods. See ConfigurableEnvironment Javadoc for usage examples demonstrating manipulation of property sources prior to application context refresh().

spring - override spring.profiles.active with #ActiveProfiles

Each of my tests is annotated with
#TestPropertySource(locations = "classpath:application-test.properties")
The content of application-test.properties starts with spring.profiles.active=... If this is set to testdev, it will override properties by matching properties found in application-testdev.properties and analogously if set to testuat, it will override with application-testuat.properties.
Some of the tests however really only make sense if testuat is set, so I annotated them additionally with #ActiveProfiles("testuat"). When I run it with spring.profiles.active=testdev set in application-test.properties it actually seems to ignore the testuat properties and only read the base properties and the testdev properties.
Is there a way to override spring.active.profiles with #ActiveProfiles ?
Thanks for the help
By default the #ActiveProfiles takes precedence over the spring.actives.profiles setting, however you can implement your own ActiveProfilesResolver with more flexible behavior. Below is an example implementation that defaults to test and optionally adds a second profile if set as a System property.
Replace #ActiveProfiles("testuat") with #ActiveProfiles(resolver = SpringActiveProfilesResolver.class)
And add this class somewhere in your test codebase:
public class SpringActiveProfilesResolver implements ActiveProfilesResolver {
#Override
public String[] resolve(Class<?> aClass) {
List<String> springProfiles = Lists.newArrayList("test");
String systemSpringProfile = System.getProperty("spring.profiles.active");
if(StringUtils.isNotBlank(systemSpringProfile)) {
springProfiles.add(systemSpringProfile);
}
return springProfiles.toArray(String[]::new);
}
}

Spring profiles on integration tests class

we have selenium tests which are ran by java test class.
On local environment everything is ok, but I want to switch off those tests when run on jenkins.
So I use:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebIntegrationTest("server.port=1234")
#Profile("!jenkins")
#ActiveProfiles("integrationtests")
public class LoginAndEditProfileSeleniumTest {
...
What works:
running mvn clean test run all tests locally, with integrationtests profile active. I dont want to pass any additional parameter.
What I want to achieve:
running mvn clean test -Dspring.profiles.active=jenkins switch off this test.
Can I merge somehow profile passed by parameter, ActiveProfile annotation and take Profile annotation into consideration? :)
//update:
Its possible to use class extending ActiveProfilesResolver:
public class ActiveProfileResolver implements ActiveProfilesResolver {
#Override
public String[] resolve(Class<?> testClass) {
final String profileFromConsole = System.getProperty("spring.profiles.active");
List<String> activeProfiles = new ArrayList<>();
activeProfiles.add("integrationtests");
if("jenkins".contains(profileFromConsole)){
activeProfiles.add("jenkins");
}
return activeProfiles.toArray(new String[activeProfiles.size()]);
}
}
but it seems to not to cooperate with #Profile anyway ( jenkins profile is active but test is still running ) .
#Profile has zero affect on test classes. Thus, you should simply remove that annotation.
If you want to enable a test class only if a given system property is present with a specific value, you could use #IfProfileValue.
However, in your scenario, you want to disable a test class if a given system property is present with a specific value (i.e., if spring.profiles.active contains jenkins).
Instead of implementing a custom ActiveProfileResolver, a more elegant solution would be to use a JUnit assumption to cause the entire test class to be ignored if the assumption fails.
This should work nicely for you:
import static org.junit.Assume.*;
// ...
#BeforeClass
public static void disableTestsOnCiServer() {
String profilesFromConsole = System.getProperty("spring.profiles.active", "");
assumeFalse(profilesFromConsole.contains("jenkins"));
}
Regards,
Sam (author of the Spring TestContext Framework)

How to access a Spring Boot Application's name programmatically?

I've defined an application name using the bootstrap.yml file in my spring boot application.
spring:
application:
name: abc
How can i get this application name during runtime/programmatically ?
You should be able to use the #Value annotation to access any property you set in a properties/YAML file:
#Value("${spring.application.name}")
private String appName;
#Autowired
private ApplicationContext applicationContext;
...
this.applicationContext.getId();
Please, find this:
# IDENTITY (ContextIdApplicationContextInitializer)
spring.application.name=
spring.application.index=
In Spring Boot Reference Manual.
And follow with source code for that ContextIdApplicationContextInitializer class:
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.setId(getApplicationId(applicationContext.getEnvironment()));
}
Where the default behavior is with this:
/**
* Placeholder pattern to resolve for application name
*/
private static final String NAME_PATTERN = "${vcap.application.name:${spring.application.name:${spring.config.name:application}}}";
Since the #Value annotation is discouraged in Spring Boot when referencing configuration properties, and because applicationContext.getId(); doesn't always return the value of spring.application.name another way is to get the value from the Environment directly
private final Environment environment;
...
public MyBean(final Environment environment) {
this.environment = environment;
}
...
private getApplicationName() {
return this.environment.get("spring.application.name");
}
Another possible way would be to create your own ConfigurationProperties class to get access to the value.
I'm not saying these are the best ways, and I hope/wish that there is a better way, but it is a way.
Note! If your using a SpringBootTest, you need to suplly the properties/yml.
Otherwise, the environment/appcontext does not load the config files.
The, your app name is not set.
Like so:
#PropertySource("classpath:application.properties")
#RunWith(SpringRunner.class)
#SpringBootTest
....
This post is aged but I hate unanswered questions.
So use the following snippet:
#Value("${spring.application.name [: defaultValue]}")
private String appName;
What is between [] is optional.
So I found a really ugly way to do this, but it works so I'm not searching further. Maybe this will help someone.
The basic premise is that spring Environment stores the value inside a propertySource.. It appears that bootstrap config is stored in the ResourcePropertySource and so you can get it from that. For me it is currently throwing an exception, but then I can get the value out of the exception, so I haven't looked any further:
try {
this.environment.getProperty("name", ResourcePropertySource.class);
} catch (ConversionFailedException e) {
String res = (String)e.getValue();
}
And then you can just do this for every property you are interested in.
Like I said ugly, but it works.

Retrieve and Modify the #ConfigurationContext programmatically via code?

How to Retrieve and Modify the #ConfigurationContext programmatically via code ?
I have a default configuration where it contains valid xml files.
Now i need to add an invalid configuration for a particular test case and test the same.
How to override, retrieve and modify the #ConfigurationContext programmatically via code ?
Thanks in advance,
Kathir
Disclaimer: I am assuming you are using JUnit since you didn't comment differently in your reply to my comment.
I think what you are trying to do does not make lot of sense, in my opinion it is still better to create a dedicated test class for your not-working configuration in order to be able to do more than one test. However:
annotate your test class with #RunWith(SpringJUnit4ClassRunner.class) and #ContextConfiguration(locations = {"classpath:/working-context.xml"}). In this way you can retrieve the configuration context in two ways: first, you can simply declare a field #Inject ApplicationContext context which will contain the working context. Or, you make your test class implements ApplicationContextAware and then write a public void setApplicationContext (ApplicationContext applicationContext). I would go for the second one since it will come in hand for changing the context programmatically.
write a not-working-context.xml and place it in your classpath
in the test method you want to fail, reload the application context with context = setApplicationContext(new ClassPathXmlApplicationContext("not-working-context.xml")); and test all the errors you like.
though it is not good practice to stand on test case order, make sure your failing test will be executed as the last one (tests are executing alphabetically) so you don't have to reload the working context in the other tests.
In the end your test class will look like:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:/working-context.xml"})
public class TestClass implements ApplicationContextAware {
private ApplicationContext context;
public void setApplicationContext(ApplicationContext context){
this.context = context;
}
//Other tests
#Test
public void zFailingTest() {
context = setApplicationContext(new ClassPathXmlApplicationContext("not-working-context.xml"));
//your test
}
}

Resources