Stop Spring Boot Test from hitting #PostContruct of SpringBootApplication class - java

I have a SpringBootApplication class that has a #PostConstruct method like that (it initializes the type of database connection):
#SpringBootApplication
public class SpringBootApp extends WebMvcConfigurerAdapter {
public static boolean workOffline = false;
private boolean setupSchema = false;
private IGraphService graphService;
private DbC conf;
#Autowired
public SpringBootApp(IGraphService graphService, DbC conf)
{
this.graphService = graphService;
this.conf = conf;
}
public static void main(String[] args) throws Exception {
SpringApplication.run(SpringBootApp.class, args);
}
#PostConstruct
public void initializeDB() {
if (workOffline) {
conf.setupOfflineEnvironment();
return;
}
else {
conf.setupProdEnvironment();
}
if (setupSchema) {
graphService.setupTestUsers();
}
}
}
I am also using Spring Boot Tests that extend this base class:
#RunWith(SpringRunner.class)
#Ignore
#SpringBootTest
public class BaseTest {
#Before
public void beforeTest() {
if (SpringBootApp.workOffline) {
conf.setupOfflineEnvironment();
} else {
conf.setupTestEnvironment();
}
graphService.setupTestUsers();}
#After
public void afterTest() {
graphService.deleteAllData();
}
}
My tests are under tests/ while my source code is under src/
Unfortunately there are cases that beforeTest() will execute before #PostConstuct and there are cases that it will execute after.. Is there a way to make my tests run with #SprinbBootTest without entering/constructiong the SpringBootApp class at all?
Thanks!

As requested, here's an attempt using spring properties (without an IDE handy, so please forgive any mistakes)
You can set a property for your test using SpringBootTest#properties
#RunWith(SpringRunner.class)
#Ignore
#SpringBootTest(properties="springBootApp.workOffline=true")
public class BaseTest {
#Before
public void beforeTest() { /* setup */ }
#After
public void afterTest() { /* teardown */ }
}
Now that we know that we can set the spring property in the test, we can set up the application to use the property:
#SpringBootApplication
public class SpringBootApp extends WebMvcConfigurerAdapter {
#Value("${springBootApp.workOffline:false}")
private boolean workOffline = false;
#Value("${springBootApp.setupSchema:false}")
private boolean setupSchema = false;
#PostConstruct
public void initializeDB() {
if (workOffline) {
// ...
} else {
// ...
}
if (setupSchema) {
// ...
}
}
}
As I've written this answer I've noticed a couple of things:
If you're only setting workOffline so you can run tests, then you probably just want to move the database setup actions out of the application and into the BaseTest class instead.
Same goes for setupSchema
If you are executing sql for your tests, don't forget about #Sql and #SqlGroup: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/integration-testing.html#__sql
Anyway, good luck!

Related

How to configure #MockBean before #PostConstruct?

I have a service that has a DataProvider which I want to mock.
Problem: the service uses the data provider in #PostConstruct. But when I use #MockBean, the mocked values are not jet present in #PostConstruct.
What could I do?
#Service
public class MyService {
private List<Object> data;
#Autowired
private DataProvider dataProvider;
#PostConstruct
public void initData() {
data = dataProvider.getData();
}
public void run() {
System.out.println(data); //always null in tests
}
}
#SpringBootTest
public class Test {
#MockBean
private DataProvider dataProvider;
#Test
public void test() {
when(dataProvider.getData()).thenReturn(mockedObjects);
//dataProvider.init(); //this fixes it, but feels wrong
service.run();
}
}
IMHO unit testing MyService would be a better solution for this particular scenario (and I wouldn't feel wrong about calling initService manually in that case), but if you insist...
You could simply override the DataProvider bean definition for this particular test and mock it beforehand, sth like:
#SpringBootTest(classes = {MyApplication.class, Test.TestContext.class})
public class Test {
#Test
public void test() {
service.run();
}
#Configuration
static class TestContext {
#Primary
public DataProvider dataProvider() {
var result = Mockito.mock(DataProvider.class);
when(result.getData()).thenReturn(mockedObjects);
return result;
}
}
}
You might need to set spring.main.allow-bean-definition-overriding to true for the above to work.

Spring dynamic #Bean registration with SpringApplicationBuilder failing in test

Trying to register beans dynamically via SpringApplicationBuilder class and it's working when running the app, but when trying to execute the test and trying to verify that the beans are defined in the context, they fail for the dynamic bean. Feel like I have to use another "magical" annotation for the tests for them to properly load the dynamic beans.
This is the code used and if you run the tests you will see that both cases will fail. BarService will fail also because FooService is registered dynamically via builder, but if you would remove the dependency it will pass the BarService test.
SpringApp.java
class FooService {
}
#Component
class BarService {
private final FooService fooService;
BarService(FooService fooService) {
this.fooService = fooService;
}
}
#SpringBootApplication
public class SpringApp {
public static void main(String[] args) {
new SpringApplicationBuilder()
.sources(SpringApp.class)
.initializers((ApplicationContextInitializer<GenericApplicationContext>) context -> {
context.registerBean(FooService.class);
})
.run(args);
}
}
SpringAppTest.java
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = SpringApp.class)
public class SpringAppTest {
#Autowired
ApplicationContext context;
#Test
public void barService() {
Assert.assertNotNull("The barService should not be null", context.getBean(BarService.class));
}
#Test
public void contextLoads() {
Assert.assertNotNull("The fooService should not be null", context.getBean(FooService.class));
}
}
First solution
The main error here is that the test does not have the initalization logic that the main method has. Solution is to extract the logic from the initializers method
class MyInitializer implements ApplicationContextInitializer<GenericApplicationContext> {
#Override
public void initialize(GenericApplicationContext context) {
System.out.println("Called initialize");
context.registerBean(FooService.class);
}
}
and use it in main
public static void main(String[] args) {
new SpringApplicationBuilder()
.sources(SpringApp.class)
.initializers(new MyInitializer())
.run(args);
}
and then use the MyInitializer in the test file through #ConextConfiguration
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = SpringApp.class, initializers = MyInitializer.class)
public class SpringAppTest {
// ...
}
Second (better) solution
Now, this can be cumbersome as we need to reference this initializer in every test, but there is an even better solution. We can create a specific Spring file resources/META-INF/spring.factories and put inside of it a reference to the initializer:
org.springframework.context.ApplicationContextInitializer=com.acme.orders.MyInitializer
After that, we can simplify both the main method
#SpringBootApplication
public class SpringApp {
public static void main(String[] args) {
SpringApplication.run(SpringApp.class, args);
}
}
and the tests, so that they don't need to always import the initializer.
#RunWith(SpringRunner.class)
#SpringBootTest
public class SpringAppTest {
// ...
}
Now both the main run process and the tests will have access to all the beans.

NullPointerException when testing Singleton in Java EE

I want to test the getCitation() method using jUnit:
#Singleton
public class QuotesLoaderBean {
Properties quotes;
Properties names;
#PostConstruct
public void init() {
InputStream quotesInput = this.getClass().getClassLoader().getResourceAsStream("quotes.properties");
InputStream namesInput = this.getClass().getClassLoader().getResourceAsStream("names.properties");
quotes = new Properties();
names = new Properties();
try {
quotes.load(quotesInput);
names.load(namesInput);
} catch (IOException ex) {
Logger.getLogger(QuotesLoaderBean.class.getName()).log(Level.SEVERE, null, ex);
}
}
public Citation createCitation(String quote) {
Citation citation = new Citation();
citation.setQuote(quote);
citation.setWho(getName());
return citation;
}
public Citation getCitation() {
Citation citation = new Citation();
citation.setQuote(getQuote());
citation.setWho(getName());
return citation;
}
In the Test File I want to Inject the Singleton and use it in the test method. But then I get the NullPointerException:
public class QuoteServiceTest {
#Inject
QuotesLoaderBean quotesLoaderBean;
public QuoteServiceTest() {
}
#BeforeClass
public static void setUpClass() {
}
#AfterClass
public static void tearDownClass() {
}
#Before
public void setUp() {
}
#After
public void tearDown() {
}
#Test
public void whenGetQuote_thenQuoteShouldBeReturned() {
quotesLoaderBean.getQuote();
}
}
The test method is not finished, nut I just want to show the exception that occurs when I call a method from the Singleton. In another service class i can easily inject the class and call the methods.
Injection is handled by a DI-enabled container in execution time.
When you deploy your entire application, a container is set and injection works fine.
When executing unit tests, none of the services are launched, and any #Inject will end up with the variable set to null, because no container will be launched either.
So, in order to test your code, you may want to build the service inside setUp method:
public class QuotesServiceTest {
QuotesLoaderBean quotesLoaderBean;
// ...
#Before
public void setUp() {
quotesLoaderBean = new QuotesLoaderBean();
// call init method after construction
quotesLoaderBean.init();
}
// ...
}

How to read spring boot property file in service class method called by unit test

I have spring boot application. I want to write some unit test for methods in service class.
I can load Environment variable and get properties in unit test class but can't do it in service class. Environment in service class is always null when reaching it from unit tests. It work when reaching it from application.
SomethingServiceTest.java
#RunWith(SpringRunner.class)
#DataJpaTest
#TestPropertySource(value = "/application.properties")
public class SomethingServiceTest {
private ISomethingService m_SomethingService;
#PostConstruct
public void setup() {
m_SomethingService = new SomethingService();
m_SomethingService.setSomethingRepository(somethingRepository);
// somethingRepository is mocked class, probably not important
}
#Test
public void test_somethingMethod() {
System.out.println(env.getProperty("some.property"));
//env here is full and i get wanted property
m_uploadService.doSomething();
}
ISomethingService.java
public interface ISomethingService {
doSomething();
}
SomethingService.java
#Service
public class SomethingService implements ISomethingService {
#Value("${some.property}")
private String someProperty;
private ISomethingRepository somethingRepository;
#Autowired
public ISomethingRepository getSomethingRepository() {
return somethingRepository;
}
public void setSomethingRepository(ISomethingRepository p_somethingRepository) {
somethingRepository = p_somethingRepository;
}
#Autowired
private Environment env;
#Override
#Transactional
public String doSomething(){
System.out.println(env.getProperty("some.property"));
//env is null here
return someProperty;
}
}

Spring Aspect Advice #Test methods

I have a test class :
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = IntegrationTestConfig.class)
#Transactional
#Component
public class AppTest {
#Autowired
private SimpleService simpleService;
#Test
public void test() {
simpleService.test();
}
}
I want make an aspect in order to monitor test method invocation
#Aspect
public class TestAspect {
#Pointcut("#annotation(org.junit.Test)")
public void pointcut() {
}
#After("pointcut()")
public void monitor() {
System.out.println("*** AFTER TEST ***");
}
}
Config :
#Configuration
#ImportResource("classpath:context.xml")
public class IntegrationTestConfig {
#Bean
public TestAspect testAspect() {
return new TestAspect();
}
}
But my monitor method has not been invoked, what's wrong? Generally - can I advice test methods?? I want to know which test method has been invoked.
JUnit itself has Rules that can be used for this.

Categories

Resources