Junit set system property for test not working - spring

I have an issue that I would have thought I could resolve by now...
I'm writing a few simple tests to hit a couple services...
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
public class EndpointTests {
private static Logger log = Logger.getLogger(ApplicationController.class.getName());
#Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
#Mock
private ApplicationController applicationController = new ApplicationController();
static {
System.setProperty("audit.enabled", "false");
}
#BeforeClass
public static void setupProperties() {
System.setProperty("audit.enabled", "false");
}
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}
#Test
public void contextLoads() {}
#Test
public void testGetApplications() throws Exception {
mockMvc.perform(get("/applications/")).andExpect(status().isOk())
.andDo(print())
.andExpect(content().contentType(TestUtils.APPLICATION_JSON_UTF8))
.andExpect(jsonPath("$.[0].name",is("XYZ")));
}
Long story short, I need this property disabled when running tests. I've tried setting the property in a static initializer and in #BeforeClass as I've seen on other posts but when it goes into the actual method, it's still its default 'enabled' value and the tests fail. I'm not using XML configuration so would prefer a code/annotation solution.
Any suggestions on another way I can fix this? Thanks.
UPDATE
Seems like every time my integration test runs:
#Test
public void testGetApplications() throws Exception {
mockMvc.perform(get("/applications/")).andExpect(status().isOk())
.andDo(print())
.andExpect(content().contentType(TestUtils.APPLICATION_JSON_UTF8))
.andExpect(jsonPath("$.[0].name",is("paasport-services")));
}
It executes my #Configuration classes on the call to mockMvc.perform...
#Configuration
public class AppConfiguration
...
...
So setting the property value in my test class does no good.
Is there any way to get in between and set this one property for my tests? I don't want to really create a separate test application context as it's just one property and everything has been working well for me up to this point.
Thanks.

I'm sure there's a much more elegant solution but I simply set a new system property in #BeforeClass on my test class.
My audit is handled by an aspect and I simply check that property I set only in my test class. If it's set to true, the advice doesn't execute.

Related

Spring MockMVC Test weird behaviour. single vs "all" execution

I am currently experiencing a weird issue regarding Spring's MockMvc in combination with Spring Security in JUnit Tests.
When I run a whole class of tests, everything works fine and the tests are passing.
But when I run All Tests in the Project one test is always failing in that certain class and it doesn't matter if I remove the test method failing, then another one fails in the same class.
#SpringBootTest
#ExtendWith({RestDocumentationExtension.class, SpringExtension.class})
class AuthenticationApiTest {
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
private static ObjectMapper objectMapper;
#BeforeAll
static void beforeAll() {
objectMapper = new ObjectMapper();
}
#BeforeEach
void setUp(RestDocumentationContextProvider documentation) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext)
.apply(documentationConfiguration(documentation)
.operationPreprocessors()
.withRequestDefaults(prettyPrint(),removeHeaders("Content-Length","Host","Pragma","X-XSS-Protection","Expires","X-Frame-Options","X-Content-Type-Options","Cache-Control"))
.withResponseDefaults(prettyPrint(),removeHeaders("Content-Length","Host","Pragma","X-XSS-Protection","Expires","X-Frame-Options","X-Content-Type-Options","Cache-Control")))
.apply(springSecurity())
.build();
}
The test which fails:
#Test
public void testSignUpFail() throws Exception {
SignUpBody signUpBody = new SignUpBody();
signUpBody.setPassword("demo1234");
signUpBody.setFirstname("first");
signUpBody.setLastname("lastn");
this.mockMvc.perform(post("/auth/user/signup")
.header(HttpHeaders.CONTENT_TYPE,"application/json")
.content(objectMapper.writeValueAsString(signUpBody))
).andExpect(status().is(400))
.andDo(document("user-signup-fail"));
this.mockMvc.perform(post("/auth/vendor/signup")
.header(HttpHeaders.CONTENT_TYPE,"application/json")
.content(objectMapper.writeValueAsString(signUpBody))
).andExpect(status().is(400))
.andDo(document("vendor-signup-fail"));
}
Any suggestions on how to solve this weird issue?

Passing an external property to JUnit's extension class

My Spring Boot project uses JUnit 5. I'd like to setup an integration test which requires a local SMTP server to be started, so I implemented a custom extension:
public class SmtpServerExtension implements BeforeAllCallback, AfterAllCallback {
private GreenMail smtpServer;
private final int port;
public SmtpServerExtension(int port) {
this.port = port;
}
#Override
public void beforeAll(ExtensionContext extensionContext) {
smtpServer = new GreenMail(new ServerSetup(port, null, "smtp")).withConfiguration(GreenMailConfiguration.aConfig().withDisabledAuthentication());
smtpServer.start();
}
#Override
public void afterAll(ExtensionContext extensionContext) {
smtpServer.stop();
}
}
Because I need to configure the server's port I register the extension in the test class like this:
#SpringBootTest
#AutoConfigureMockMvc
#ExtendWith(SpringExtension.class)
#ActiveProfiles("test")
public class EmailControllerIT {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper objectMapper;
#Value("${spring.mail.port}")
private int smtpPort;
#RegisterExtension
// How can I use the smtpPort annotated with #Value?
static SmtpServerExtension smtpServerExtension = new SmtpServerExtension(2525);
private static final String RESOURCE_PATH = "/mail";
#Test
public void whenValidInput_thenReturns200() throws Exception {
mockMvc.perform(post(RESOURCE_PATH)
.contentType(APPLICATION_JSON)
.content("some content")
).andExpect(status().isOk());
}
}
While this is basically working: How can I use the smtpPort annotated with #Value (which is read from the test profile)?
Update 1
Following your proposal I created a custom TestExecutionListener.
public class CustomTestExecutionListener implements TestExecutionListener {
#Value("${spring.mail.port}")
private int smtpPort;
private GreenMail smtpServer;
#Override
public void beforeTestClass(TestContext testContext) {
smtpServer = new GreenMail(new ServerSetup(smtpPort, null, "smtp")).withConfiguration(GreenMailConfiguration.aConfig().withDisabledAuthentication());
smtpServer.start();
};
#Override
public void afterTestClass(TestContext testContext) {
smtpServer.stop();
}
}
The listener is registered like this:
#TestExecutionListeners(value = CustomTestExecutionListener.class, mergeMode = MERGE_WITH_DEFAULTS)
When running the test the listener gets called but smtpPort is always 0, so it seems as if the #Value annotation is not picked up.
I don't think you should work with Extensions here, or in general, any "raw-level" JUnit stuff (like lifecycle methods), because you won't be able to access the application context from them, won't be able to execute any custom logic on beans and so forth.
Instead, take a look at Spring's test execution listeners abstraction
With this approach, GreenMail will become a bean managed by spring (probably in a special configuration that will be loaded only in tests) but since it becomes a bean it will be able to load the property values and use #Value annotation.
In the test execution listener you'll start the server before the test and stop after the test (or the whole test class if you need that - it has "hooks" for that).
One side note, make sure you mergeMode = MergeMode.MERGE_WITH_DEFAULTS as a parameter to #TestExecutionListeners annotation, otherwise some default behaviour (like autowiring in tests, dirty context if you have it, etc) won't work.
Update 1
Following Update 1 in the question. This won't work because the listener itself is not a spring bean, hence you can't autowire or use #Value annotation in the listener itself.
You can try to follow this SO thread that might be helpful, however originally I meant something different:
Make a GreenMail a bean by itself:
#Configuration
// since you're using #SpringBootTest annotation - it will load properties from src/test/reources/application.properties so you can put spring.mail.port=1234 there
public class MyTestMailConfig {
#Bean
public GreenMail greenMail(#Value(${"spring.mail.port"} int port) {
return new GreenMail(port, ...);
}
}
Now this configuration can be placed in src/test/java/<sub-package-of-main-app>/ so that in production it won't be loaded at all
Now the test execution listener could be used only for running starting / stopping the GreenMail server (as I understood you want to start it before the test and stop after the test, otherwise you don't need these listeners at all :) )
public class CustomTestExecutionListener implements TestExecutionListener {
#Override
public void beforeTestClass(TestContext testContext) {
GreenMail mailServer =
testContext.getApplicationContext().getBean(GreenMail.class);
mailServer.start();
}
#Override
public void afterTestClass(TestContext testContext) {
GreenMail mailServer =
testContext.getApplicationContext().getBean(GreenMail.class);
mailServer.stop();
}
}
Another option is autowiring the GreenMail bean and using #BeforeEach and #AfterEach methods of JUnit, but in this case you'll have to duplicate this logic in different Test classes that require this behavour. Listeners allow reusing the code.

Spring Batch testing - Autowired bean is null

I am completely stumped. I am new to Spring Batch testing and I have found countless examples that have left me confused.
I'm trying to test a Spring Batch decider. This decider checks to see if certain JSON files exist before continuing.
To begin, I have a BatchConfiguration file marked with #Configuration in my Spring Batch project.
In the BatchConfiguration, I have a ImportJsonSettings bean which loads its properties from settings in the application.properties file.
#ConfigurationProperties(prefix="jsonfile")
#Bean
public ImportJSONSettings importJSONSettings(){
return new ImportJSONSettings();
}
When running the Spring Batch application, this works perfectly.
Next, here are the basics of the JsonFilesExistDecider , which Autowires a FileRetriever object...
public class JsonFilesExistDecider implements JobExecutionDecider {
#Autowired
FileRetriever fileRetriever;
#Override
public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) { ... }
The FileRetriever object itself Autowires the ImportJSONSettings object.
Here is the FileRetriever...
#Component("fileRetriever")
public class FileRetriever {
#Autowired
private ImportJSONSettings importJSONSettings;
private File fieldsFile = null;
public File getFieldsJsonFile(){
if(this.fieldsFile == null) {
this.fieldsFile = new File(this.importJSONSettings.getFieldsFile());
}
return this.fieldsFile;
}
}
Now for the test file. I am using Mockito for testing.
public class JsonFilesExistDeciderTest {
#Mock
FileRetriever fileRetriever;
#InjectMocks
JsonFilesExistDecider jsonFilesExistDecider;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test
public void testDecide() throws Exception {
when(fileRetriever.getFieldsJsonFile()).thenReturn(new File(getClass().getResource("/com/files/json/fields.json").getFile()));
// call decide()... then Assert...
}
}
PROBLEM... The ImportJSONSettings object that is #Autowired in the FileRetriever object is always NULL.
When calling the testDecide() method, I get a NPE since calling the getFieldsJsonFile() in FileRetriever, the ImportJSONSettings bean does not exist.
How does the ImportJSONSettings bean get properly created in the FileRetriever object so it can be used??
I have tried adding the following to my test class, but it does not help.
#Mock
ImportJSONSettings importJSONSettings;
Do I need to create it independently? How does it get injected into the FileRetriever?
Any help would be appreciated.
Try changing the #Before annotation on the setup() method to #BeforeEach like so:
#BeforeEach
void setup() {
MockitoAnnotations.initMocks(this);
}
This could also be a dependency issue. Make sure you have a recent version of io.micrometer:micrometer-core. Would you be able to share your test dependencies?
If you have the above setup correctly, you shouldn't have to worry about whether or not ImportJSONSettings is null or not as long as you have getFieldsJsonFile() stubbed correctly.

SpringRunner unable to detect configuration

I have a spring-boot application for which am trying to create unit testcases. Below is the code that I am trying to run, I don't have any configuration file that I have (used only annotations) so the main class that loads all the configuration is ElastSearchBootApplication class. For some reason I see the below error.
#ComponentScan(basePackages = "com.somename")
#SpringBootApplication
#EnableScheduling
public class ElastSearchBootApplication {
private static final Logger LOG = LoggerFactory.getLogger(ElastSearchBootApplication.class);
public static void main(String[] args) {
SpringApplication.run(ElastSearchBootApplication.class, args);
}
#Autowired
private ElastSearchLogLevel logsSearch;
#Scheduled(fixedRate = 120000)
public void scheduledSearchLogs() {
...
Test class :
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = ElastSearchBootApplication.class)
public class LogSearchTest {
#Mock
private RestHighLevelClient client;
#Mock
private ExecutorService ALERT_POOL;
#Before
public void setUp() throws Exception {
client = mock(RestHighLevelClient.class);
ALERT_POOL = mock(ExecutorService.class);
try {
when(client.search(anyObject())).thenReturn(getResponse());
} catch (Exception e) {
// I see NullPointerException but both the instances are available here
e.printStackTrace();
}
doNothing().when(ALERT_POOL.invokeAll(anyObject()));
}
I see the below error when trying to run the spring-boot test :
org.springframework.boot.test.context.SpringBootTestContextBootstrapper buildDefaultMergedContextConfiguration
INFO: Neither #ContextConfiguration nor #ContextHierarchy found for test class [com.somename.search.LogSearchTest], using SpringBootContextLoader
org.springframework.test.context.support.AbstractContextLoader generateDefaultLocations
INFO: Could not detect default resource locations for test class [com.somename.search.LogSearchTest]: no resource found for suffixes {-context.xml, Context.groovy}.
org.springframework.test.context.support.AnnotationConfigContextLoaderUtils detectDefaultConfigurationClasses
INFO: Could not detect default configuration classes for test class [com.somename.search.LogSearchTest]: LogSearchTest does not declare any static, non-private, non-final, nested classes annotated with #Configuration.
I see that #SpringBootTest is used for integration tests, so can I use it for unit tests ? If I remove it then I get another set of exception that looks similar though. I would be more interested in running this testcase without SpringBootTest
Why my test case say some configuration is missing. The samples online talk about xml files which I don't have. So what am I missing here ?
Can I dynamically pass the value for fixedRate from Environment and put it like #Scheduled(fixedRate = ${some.value.defined})
UPDATE
I can run the test but without the proper order. Ideally i expect setUp to run first. But its running second. Also the line when(client.search(anyObject())).thenReturn(getResponse()); is failing and i dont get the reason...
You have to add the annotation #ContextConfiguration to your test class to specify configuration file.
#ContextConfiguration(classes = ElastSearchBootApplication.class)
Try this:
#RunWith(SpringRunner.class)
#SpringBootTest
public class LogSearchTest {
#MockBean
private RestHighLevelClient client;
#MockBean
private ExecutorService ALERT_POOL;
#Before
public void setUp() throws Exception {
try {
when(client.search(anyObject())).thenReturn(getResponse());
} catch (Exception e) {
// I see NullPointerException but both the instances are available here
e.printStackTrace();
}
doNothing().when(ALERT_POOL.invokeAll(anyObject()));
}

Controller layer test in SpringBoot application

I have a controller in my SpringBoot app:
#Controller
#RequestMapping("/v1/item")
public class Controller{
#Autowired
private ServiceForController service;
#PostMapping()
public String createItem(#ModelAttribute Item item) {
Item i = service.createItem(item.getName(), item.getDomain());
return "item-result";
}
}
And I'd like to test it separately from service with a help of mocks.How to implement it?
There are at least two approaches to do it:
To start up the whole SpringBoot context and make a sort of integration tests
Example:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class ControllerTest {
#Autowired
private MockMvc mvc;
#Test
#WithMockUser(roles = "ADMIN")
public void createItem() throws Exception {
mvc.perform(post("/v1/item/")
.param("name", "item")
.param("domain", "dummy.url.com"))
.andExpect(status().isOk());
//check result logic
}
Test exclusive controller layer and limit the whole loaded context exclusively to it. Example:
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = Controller.class)
public class ControllerTest{
#Autowired
private MockMvc mvc;
#MockBean
private ServiceForController service;
//testing methods and their logic
...
}
Even though the second approach seems more sensible (as for me) in terms of resources used, it may cause plenty of inconveniences due to the lack of beans initialized. For instance, before I decided to try another option, I faced the need to create mocks of at least 5 beans that are added to the context on SpringBoot start in my ContollerTest class.
Thus, I had to switch to the approach with a use of #SpringBootTest in combination with #SpyBean, that allowed me to call a Mockito verify() method.

Resources