How to enable tests based on spring boot profile? - java

This is a similar but further question with how to run/turn off selective tests based on profiles in spring boot.
I know the #IfProfileValue annotation provides the ability to enable test based on some conditions, but the properties it get are from System.properties and the spring.profiles.active is not in it.
I also know we can use #ProfileValueSourceConfiguration to customise the ProfileValueSource like below:
Test
#RunWith(SpringRunner.class)
#SpringBootTest
#ProfileValueSourceConfiguration(value = SpringProfileValueSource.class)
#IfProfileValue(name = "spring.profiles.active", value = "test")
public class MyTest {
// some tests
}
Custom ProfileValueSource
public class SpringProfileValueSource implements ProfileValueSource {
#Override
public String get(String key) {
Environment environment = StaticSpringContext.applicationContext.getEnvironment();
return environment.getProperty(key);
}
}
Since the SpringProfileValueSource will be created outside of Spring, so I created the following class to get Spring context:
StaticSpringContext
#Configuration
public class StaticSpringContext implements ApplicationContextAware {
public static ApplicationContext applicationContext;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
StaticSpringContext.applicationContext = applicationContext;
}
}
However, seems all the properties check happens before Spring get started, so the applicationContext in StaticSpringContext is actually null when I use it in SpringProfileValueSource.
I thought it was a common situation to enable tests based on spring profiles, but I did many search and still don't know how to do it. Any ideas? With many thanks.

Related

Request-scoped beans not working in Spring tests with Cucumber

I have an application based on Spring 4.3.28 (i.e. not Spring Boot!) and I want to migrate my integration tests to Cucumber.
I’ve followed this tutorial and adapted it to plain Spring.
The tests I’ve written so far are working fine (Spring context is initialized etc.), but as soon as there are request-scoped beans involved, they stop working:
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you
referring to request attributes outside of an actual web request, or processing a
request outside of the originally receiving thread? If you are actually operating
within a web request and still receive this message, your code is probably running
outside of DispatcherServlet/DispatcherPortlet: In this case, use
RequestContextListener or RequestContextFilter to expose the current request.
I’ve created a small sample project
that tries to reproduce the problem.
There is one context configuration class called AppConfig:
#Configuration
public class AppConfig {
#Bean
#Scope("request“) // when this line is removed, the test succeeds
public ExampleService exampleService() {
return new ExampleService();
}
#Bean("dependency")
#Scope("request") // when this line is removed, the test succeeds
public String dependencyBean() {
return "dependency bean";
}
}
The ExampleService is request-scoped, and gets one request-scoped bean injected by #Autowired:
public class ExampleService {
#Autowired
#Qualifier("dependency")
String dependencyBean;
public String process() { return "I have a "+dependencyBean; }
}
For the tests, I have one Spring-annotated superclass:
#ContextConfiguration(classes = AppConfig.class)
#CucumberContextConfiguration
#WebAppConfiguration
public class TestBase {
#Autowired
public ExampleService underTest;
}
There’s also a plain Spring test that runs just fine:
#RunWith(SpringRunner.class)
public class ExampleServicePlainSpringTest extends TestBase {
#Test
public void whenProcessingDataThenResultShouldBeReturned() {
assertThat(this.underTest.process()).isEqualTo("I have a dependency bean");
}
}
The Cucumber test is executed by this test class stub:
#RunWith(Cucumber.class)
public class ExampleServiceCucumberTest extends TestBase {}
The actual cucumber step definitions are here:
public class CucumberStepDefinitions extends TestBase {
private String result;
#When("I process data")
public void iProcessData() {
result = this.underTest.process();
}
#Then("the result should be returned")
public void checkResult() {
assertThat(result).isEqualTo("I have a dependency bean");
}
}
The .feature file for Cucumber is in the src/test/resources directory under the same package name as the step definitions class:
Feature: Example
Scenario: Example service bean returns dependency
When I process data
Then the result should be returned
Usually when I encountered the „no thread-bound request found“ error, it was because the #WebAppConfiguration annotation was missing, or when I tried to inject a request-scoped bean into a non-request scoped bean. But that’s not the case here.
What am I doing wrong?
I was able to figure out how to resolve it; the updated code is in the github repository linked in the question.
When using the SpringRunner, the request context is initialized in a ServletTestExecutionListener that is implicitly added to the list of TestExecutionListeners for the test.
The initialization happens in the beforeTestMethod() method of that listener.
However, as #M.P.Korsanje correctly remarked in the comments (thanks!), Cucumber doesn't have test methods, so beforeTestMethod() is never executed.
My solution was to add a custom subclass of ServletTestExecutionListener as a TestExecutionListener that delegates the beforeTestClass() call to the beforeTestMethod():
public class ClassLevelServletTestExecutionListener extends ServletTestExecutionListener {
#Override
public void beforeTestClass(TestContext testContext) throws Exception {
super.beforeTestMethod(testContext);
}
#Override
public void afterTestClass(TestContext testContext) throws Exception {
super.afterTestMethod(testContext);
}
}
And in ExampleServiceCucumberTest:
#ContextConfiguration(classes = {AppConfig.class})
#CucumberContextConfiguration
#WebAppConfiguration
#TestExecutionListeners(ClassLevelServletTestExecutionListener.class)
// extend the Spring class to get the default TestExecutionListeners
public class TestBase extends AbstractJUnit4SpringContextTests {
#Autowired
public ExampleService underTest;
}

Mockito - mock ApplicationContext

I have a Springboot application that looks up the bean from the ApplicationContext at runtime based on the input parameter passed by the user. For this method, I am trying to write Mockito test cases but it is not working and throws NullPointerException.
The class which bootstraps the application:
#SpringBootApplication
public class MyApplication {
private static ApplicationContext appContext;
public static void main(String[] args) {
appContext = SpringApplication.run(MyApplication.class, args);
}
public static ApplicationContext getApplicationContext() {
return appContext;
}
}
Class for which I am trying to write the test cases:
#Service
public class Mailbox {
#Autowired
MailProcessor processor;
public void processUserInput(Envelope object) {
processor.setCommand(MyApplication.getApplicationContext().getBean(object.getAction(), Command.class));
processor.allocateEnvelopes(object);
}
}
And my test case is as below:
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest
#ActiveProfiles("test")
public class MailboxTest {
#Mock
MailProcessor processor;
#InjectMocks
Mailbox mailbox;
#Test
public void testProcessUserInput() {
Envelope message = new Envelope();
message.setAction("userAction");
message.setValue("userInput");
doNothing().when(processor).setCommand(any());
doNothing().when(processor).allocateEnvelopes(any());
mailbox.processUserInput(message);
Mockito.verify(processor).allocateEnvelopes(any());
}
}
Whenever I run the test cases it gives the NullPointerException at processor.setCommand(MyApplication.getApplicationContext().getBean(object.getAction(), Command.class)); in Mailbox class. How can I mock the ApplicationContext lookup? Am I missing any mocking step?
Spring wise your code doesn't look good, and in particular is not unit testable. I'll explain:
Your Mailbox service should not be aware of MyApplication at any level. It is an entry point of spring boot application and your business logic should not depend on that.
Its true that you can inject the application context directly into the class. See an example below. Another (more "old-school") option here is using ApplicationContextAware interface in the Mailbox service (see this example). However, its still a bad code IMO:
#Service
public class Mailbox {
private final ApplicationContext ctx;
...
public Mailbox(ApplicationContext ctx) {
this.ctx = ctx;
}
...
}
Even if you resolve it, in general its not a good idea to depend on the ApplicationContext as well. Because this way you become spring dependent and there is no reason to do that in the Mailbox class. The class will become unit testable though.
In terms of resolution:
In spring you can inject a Map<String, Command> into the mailbox (Its a built-in feature in spring) so that the key of the map will be a bean name, exactly an action of your envelop.
So here is the solution (simplified in places not relevant to injection, just to illustrate the idea):
public interface Command {
void execute();
}
#Component("delete") // note this "delete" word - it will be a key in the map in the Mailbox
public class DeleteMailCommand implements Command {
#Override
public void execute() {
System.out.println("Deleting email");
}
}
#Component("send")
public class SendMailCommand implements Command{
#Override
public void execute() {
System.out.println("Sending Mail");
}
}
Note, that all the commands must be driven by spring (which seems to be your case anyway).
Now, the Mailbox will look like this:
#Service
public class Mailbox {
private final Map<String, Command> allCommands;
private final MailProcessor processor;
// Note this map: it will be ["delete" -> <bean of type DeleteMailCommand>, "send" -> <bean of type SendMailCommand>]
public Mailbox(Map<String, Command> allCommands, MailProcessor mailProcessor) {
this.allCommands = allCommands;
this.processor = mailProcessor;
}
public void processUserInput(Envelope envelope) {
Command cmd = allCommands.get(envelope.getAction());
processor.executeCommand(cmd);
}
}
This solution is easily unit testable, because you can populate the map with mock commands if you wish and there is no need to deal with the application context.
Update
I took a look on your test now, and it's also not really good, sorry :)
#RunWith(MockitoJUnitRunner.class) is used to run unit tests (without spring at all). There is no point in placing this annotation in conjunction with #SpringBootTest which runs a full-fledged system test: starts the whole spring boot application, loads configurations and so forth.
So make sure what kind of tests you want to run and use the appropriate annotations.
Can't say for sure without debugging but it looks like MyApplication.getApplicationContext() is returning null.
Instead of storing it in a static variable you should try injecting the ApplicationContext in your #Service class where you need it:
#Autowired
private ApplicationContext appContext;
Try initializing mailbox object by injecting processor before first test.
mailbox = new Mailbox(processor);

When i use Spring Boot with shiro , Spring Boot Actuator doesnt publish jvm metric

I use Spring Boot with Shiro , but i starting Application and navigating to /actuator/metrics displays a list of available meter names:
{
names: [
"http.server.requests",
"jdbc.connections.max",
"jdbc.connections.min"
]
}
Just 3 items!!!
Then , i shield shiro config :
//#Configuration
public class ShiroConfig {
...
}
I starting Application again and i get all:
{
names: [
"jvm.memory.max",
"process.files.max",
"jvm.gc.memory.promoted",
"tomcat.cache.hit",
"system.load.average.1m",
"tomcat.cache.access",
"jvm.memory.used",
"jvm.gc.max.data.size",
"jdbc.connections.max",
"jdbc.connections.min",
"jvm.gc.pause",
"jvm.memory.committed",
"system.cpu.count",
"logback.events",
"tomcat.global.sent",
"jvm.buffer.memory.used",
"tomcat.sessions.created",
"jvm.threads.daemon",
"system.cpu.usage",
"jvm.gc.memory.allocated",
"tomcat.global.request.max",
"tomcat.global.request",
"tomcat.sessions.expired",
"jvm.threads.live",
"jvm.threads.peak",
"tomcat.global.received",
"process.uptime",
"tomcat.sessions.rejected",
"process.cpu.usage",
"tomcat.threads.config.max",
"jvm.classes.loaded",
"jvm.classes.unloaded",
"tomcat.global.error",
"tomcat.sessions.active.current",
"tomcat.sessions.alive.max",
"jvm.gc.live.data.size",
"tomcat.servlet.request.max",
"tomcat.threads.current",
"tomcat.servlet.request",
"process.files.open",
"jvm.buffer.count",
"jvm.buffer.total.capacity",
"tomcat.sessions.active.max",
"tomcat.threads.busy",
"process.start.time",
"tomcat.servlet.error"
]
}
I want to use both Actuator and Shiro.
Thanks for any helps.
As I expected, this problem is caused by the loading order of the beans.
I used Shiro in the project.
Shiro's verification method used MyBatis to read data from the database.
I used #Autowired for MyBatis' Mapper file, which caused the Actuator metrics related beans to not be assembled by SpringBoot (I don't know what the specific reason is).
So i disabled the automatic assembly of the Mapper file by manual assembly.
The code is as follows:
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
SpringContextUtil.applicationContext = applicationContext;
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public static Object getBean(String beanId) throws BeansException {
return applicationContext.getBean(beanId);
}
}
Then
StoreMapper userMapper = (UserMapper) SpringContextUtil.getBean("userMapper");
UserModel userModel = userMapper.findUserByName(name);
The problem can be solved for the time being. This is just a stopgap measure, but at the moment I have no better way.

Spring: create mocks instead of real objects

I'm using Spring annotation based configuration in my Play application.
Controllers and DAOs are Spring beans. Controller and DAO layers are defined with different Spring profiles and each layer could be disabled separately.
I'd like to test controller layer in isolation from DAO layer. I've disabled DAO profile and redefined each of DAO beans as a Mockito mock. From functional point of view it works fine, the only thing I don't like is defining mocks manually like this:
#Configuration
#Import(AppContext.class)
public class TestAppContext {
#Bean
public DaoA getDaoA(){
return mock(DaoA.class);
}
//... all dependencies are re-defined manually
}
Is there a way to define package (like with #ComponentScan annotation)
and get all beans in that package as mocks instead of real objects?
UPD:
I'm running tests with FakeApplication (https://www.playframework.com/documentation/2.0/api/java/play/test/FakeApplication.html), so context is started not in the test level, but inside fake application startup.
public class ControllerTest extends WithApplication {
#Before
public void setUp() throws Exception {
start(fakeApplication(new GlobalSettings(){
private ApplicationContext appContext;
public void onStart(Application app) {
appContext = new AnnotationConfigApplicationContext(TestAppContext.class);
}
#Override
public <A> A getControllerInstance(Class<A> clazz) throws Exception {
return appContext.getBean(clazz);
}
}));
}
...
}
I did it like this because I wan't to make the test more reliable and test how controller works in real environment:
#Test
public void testControllerMethod() {
Result result = route(fakeRequest(GET, "/controller/method"));
assertThat(result).is(...);
}
If the number of dependencies you need to mock is huge, you can also use spring-auto-mock.
#ContextConfiguration(classes = { AutoMockRegistryPostProcessor.class, RestOfClasses.class, ... })
#RunWith(SpringJUnit4ClassRunner.class)
public class YourTest {
...
}
As you are creating the ApplicationContext on your own, you can register the postprocessor programmatically:
public void onStart(Application app) {
appContext = new AnnotationConfigApplicationContext(TestAppContext.class);
appContext.getBeanFactory().addBeanPostProcessor(new AutoMockRegistryPostProcessor())
}
Mark your unit-test with #RunWith(SpringJUnit4ClassRunner.class)
Mark your tested class as #InjectMock
Mark you Dao class as #Mock
Make use of Mockito in your project

Spring default wiring with Profile

I've got two beans. Both implement the mailing function. One is only working when it is deployed to an application server. The other one is used for testing.
We have profile for each developer and environment. I want to wire the testing bean only when actually testing. The other bean should be used when not testing. How can I archive this?
#Component
#Profile("localtest")
public class OfflineMail implements Mailing {}
Solution approaches:
Using "default" I read this somewhere, but there seems to be no fall-back to "default" for a profile like "dev":
#Component
#Profile("default")
public class OnlineMail implements Mailing {}
-> Exception for no bean for wiring found.
Leaving the profile out:
#Component
public class OnlineMail implements Mailing {}
-> Throws a unique exception when running the "localtest" profile.
Adding all profiles:
#Component
#Profile("prod")
#Profile("integration")
#Profile("test")
#Profile("dev1")
#Profile("dev2")
#Profile("dev3")
...
public class OnlineMail implements Mailing {}
This is actually working, however our devs aren't numbered they use "dev<WindowsLogin>" and adding the profiles, may work for one bean, but one will get into trouble when using it for several beans as this definitely gets ugly.
Using something like #Profile("!localtest") doesn't seem to work as well.
Does anyone know a nicer way to get a "wire by default if no specific bean is found"?
I finally found an easy solution.
The online mail is just wired by default.
#Component
public class OnlineMail implements Mailing {}
Using the #Primary annotation the offline mail takes precedence over the OnlineMail and avoids the Unique exception.
#Component
#Profile("localtest")
#Primary
public class OfflineMail implements Mailing {}
Try this:
#Component
#Profile("production")
public class OnlineMail implements Mailing {}
#Component
#Profile("localtest")
public class OfflineMail implements Mailing {}
Then run tests using #ActiveProfiles("localtest") and run production enviroment using "production" as DEFAULT profile.
Also I hope in next version of Spring ActiveProfilesResolver will be introduced SPR-10338 - it may be helpfull for you (to avoid "dev1", "dev2" and so on).
Spring supports inject the Bean by #Profile very well:
interface Talkative {
String talk();
}
#Component
#Profile("dev")
class Cat implements Talkative {
public String talk() {
return "Meow.";
}
}
#Component
#Profile("prod")
class Dog implements Talkative {
public String talk() {
return "Woof!";
}
}
Works in unit test
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:applicationContex-test.xml"})
#ActiveProfiles(value = "dev")
public class InjectByDevProfileTest
{
#Autowired
Talkative talkative;
#Test
public void TestTalkative() {
String result = talkative.talk();
Assert.assertEquals("Meow.", result);
}
}
Works in Main():
#Component
public class Main {
public static void main(String[] args) {
// Enable a "dev" profile
System.setProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, "dev");
ApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
Main p = context.getBean(Main.class);
p.start(args);
}
#Autowired
private Talkative talkative;
private void start(String[] args) {
System.out.println(talkative.talk());
}
}
Check this for the Demo code: https://github.com/m2land/InjectByProfile

Categories

Resources