Use Spring Data random (embedded) Mongo port with NoSQL JUnit #Rule - java

I'm currently trying to write an Integration test class which uses Spring Data Mongo repositories.
I use an embedded Mongo instance provided by the de.flapdoodle.embed.mongo dependency. Spring Data documentation specifies that we only have to put this dependency in the project and the EmbedMongoAutoConfiguration takes care of the rest.
For now, that's ok, and setting the port to 0 makes the auto configuration process to find a free port to launch the mongo instance on.
This feature is necessary for me to avoid collision with other tests (which are run on a Jenkins CI server along with other project of my company).
The problem comes now, I want to be able to inject some test data from some external file before each of my test method run. I found out that NoSQL Unit can do this with a simple method annotation and a JUnit #Rule.
Here is an example:
#Value("${local.mongo.port}")
private int mongoPort; // <- still 0 a the time the Rule below is created.
#Rule
public MongoDbRule managedMongoDb = new MongoDbRule(MongoDbConfigurationBuilder.mongoDb().databaseName("myAwesomeDb").port(mongoPort).build());
#Test
#UsingDataSet(locations = "testdata.json", loadStrategy = LoadStrategyEnum.CLEAN_INSERT)
public void testMyData() {
// ...
}
My problem is that, the #Rule needs the Mongo port in its builder to instantiate the underlying MongoClient, but at the time the #Rule is instantiated, the Spring context is not fully initialized and the EmbeddedMongoAutoConfiguration has not published the port yet.
So my question is, is there anyone who has ever used the Embedded Mongo feature with NoSQL Unit, and is there any way to, for example create the #Rule after the Spring context is initialized ?
I was wondering if finding the free port myself (in a static way), setting it to the #Rule and then tell the EmbeddedMongoAutoConfiguration to use it by overriding the IMongodConfig bean was a good idea ? or is there a "simpler" way ?
Note: I just saw that flapdoodle library provides a class and a static method to find a free server port and its used by Spring like this:
Network.getFreeServerPort(getHost()), Network.localhostIsIPv6()))
Thanks everyone in advance!
EDIT:
I tried the solution I talked just above, and it seems to work, though I still think it's a bit "verbose" and dirty.
private static final Logger log = LoggerFactory.getLogger(MyAwesomeIT.class);
private static int mongoPort;
static {
try {
mongoPort = Network.getFreeServerPort();
} catch (IOException e) {
log.error("Error while trying to find a free port for Mongo", e);
mongoPort = -1; // test should then not work
}
}
#Rule
public MongoDbRule managedMongoDb = new MongoDbRule(MongoDbConfigurationBuilder.mongoDb().databaseName("myAwesomeDb").port(mongoPort).build());
then in the associated configuration class :
#Configuration
#EnableAutoConfiguration
#EnableMongoRepositories
#EnableConfigurationProperties(MongoProperties.class)
static class ContextConfiguration {
#Autowired
private MongoProperties mongoProperties;
#PostConstruct
public void init() {
// Here, I override the port property
mongoProperties.setPort(mongoPort);
}
}

Refining the solution given by #user6599111, it is possible to obtain the port randomly chosen by Flapdoodle Embedded Mongo, simply injecting an object of type IMongodConfig.
Spring Boot builds automagically this object for you, as stated here.
Then, the configuration class will become the following.
#Configuration
#EnableAutoConfiguration(exclude = { EmbeddedMongoAutoConfiguration.class })
public class MongoConfiguration {
#Autowired
private Environment environment;
#Autowired
private MongoProperties properties;
#Autowired(required = false)
private MongoClientOptions options;
#Autowired
private IMongodConfig mongoConfig;
#Bean
public MongoClient mongo() throws Exception {
properties.setPort(mongoConfig.net().getPort());
return properties.createMongoClient(this.options, this.environment);
}
}

I had the same problem and this was my solution
#Configuration
public class EmbeddedMongoConfig extends AbstractMongoConfiguration {
#Autowired
private Environment environment;
#Autowired
private MongoProperties properties;
#Autowired(required = false)
private MongoClientOptions options;
#Override
protected String getDatabaseName() {
return properties.getDatabase();
}
#Override
#Bean(destroyMethod = "close")
public Mongo mongo() throws Exception {
properties.setPort(Network.getFreeServerPort());
return properties.createMongoClient(this.options, this.environment);
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = { AppRunner.class, EmbeddedMongoConfig.class })
public class BaseTest {
}
public class CategoryServiceTest extends BaseTest{
#Autowired
private CategoryService categoryService;
#Test
public void someTest(){
fail("Test not implemented");
}
}

I have tried this:
int mongoPort = SocketUtils.findAvailableTcpPort();
Source:
https://docs.spring.io/spring/docs/4.0.5.RELEASE/javadoc-api/org/springframework/util/SocketUtils.html
This worked for me.
We had embedded Mongo running for unit tests and where there were multiple applications building on Jenkins, some of them failed since the port was same for everyone. Manually changing the ports was also tried but since there were many applications and some of them used a common base class, it was failing.
I am not sure about the #Rule part but you can try this.

Related

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);

How to create a test in Springt Boot that calls a service which includes constructor injection?

I'm trying to write a test in a Spring-Boot project. My problem is that I can't use my service that includes a constructor injection.
Depending on what I try I get errors like java.lang.IllegalStateException: Failed to load ApplicationContexts or NullPointerExceptions.
My first try was to change the constructor injection in my service to a field injection. But after reading this post I decided to change it back to the previous way.
Then I searched for examples but couldn't find something that was helpful.
Following are the relevant code snippets. If more code is needed I would provide it.
The service class with the constructor injection:
PlayerServiceImpl.java
#Service
public class PlayerServiceImpl implements PlayerService {
private PlayerRepository playerRepository;
private CompanyService companyService;
private CompanyResourceService companyResourceService;
#Autowired
public PlayerServiceImpl(PlayerRepository thePlayerRepository, CompanyService theCompanyService,
CompanyResourceService theCompanyResourceService) {
this.playerRepository = thePlayerRepository;
this.companyService = theCompanyService;
this.companyResourceService = theCompanyResourceService;
}
...
}
The test class im trying to create:
PlayerServiceImplIntegrationTest.java
#RunWith(SpringRunner.class)
#SpringBootTest
public class PlayerServiceImplIntegrationTest {
#TestConfiguration
static class PlayerServiceImplTestContextConfiguration {
private PlayerRepository playerRepository;
private CompanyService companyService;
private CompanyResourceService companyResourceService;
#Bean
public PlayerService playerService() {
return new PlayerServiceImpl(playerRepository, companyService, companyResourceService);
}
}
#Autowired
private PlayerService playerService;
#MockBean
private PlayerRepository playerRepository;
#Before
public void setUp() {
Player max = new Player("MaxMustang", "test123", "MaxMustang",
"max.mustang#test.com", new Date(System.currentTimeMillis()), 1, 0,
new BigDecimal("0.00"), new BigDecimal("0.00"), 0, 0);
Mockito.when(playerRepository.findByUserName(max.getUserName()))
.thenReturn(max);
}
#Test
public void whenFindById_thenReturnPlayer() {
String userName = "MaxMustang";
Player found = playerService.findByUserName(userName);
assertThat(found.getUserName()).isEqualTo(userName);
}
}
In my test, I'm trying to create a player object and receive it. It's just my first test in Spring Boot. And my main goal was to just get the test running.
And the original test is from Baeldung from "5. Mocking with #MockBean". But while experimenting around, I added or changed a few things.
If I missed a post pointing at the same problem I would be glad to be informed about that.
Also, I would appreciate it if someone can tell me if the arguments in the constructor of my service are too much or still in an "ok" range.
You have to make the configuration bean primary and also use constructor injection on that method:
#TestConfiguration
static class PlayerServiceImplTestContextConfiguration {
#Bean
#Primary
public PlayerService playerService(PlayerRepository playerRepository,
CompanyService companyService, CompanyResourceService companyResourceService) {
return new PlayerServiceImpl(playerRepository, companyService, companyResourceService);
}
}
Without primary you will have two beans of same type floating around and you dont use #Qualifier here. Also you cannot #Autowire beans in a configuration class thats why you need to use constructor injection.

Spring boot testing of a rest client using #RestClientTest

I am using spring boot 1.5.8 and want to test my client:
#Component
public class RestClientBean implements RestClient {
private Map<String, RestTemplate> restTemplates = new HashMap<>();
#Autowired
public RestClientBean(RestTemplateBuilder builder, SomeConfig conf) {
restTemplates.put("first", builder.rootUri("first").build();
restTemplates.put("second", builder.rootUri("second").build();
}
}
with the following test:
#RunWith(SpringRunner.class)
#RestClientTest(RestClient.class)
public class RestClientTest {
#Autowired
private RestClient client;
#Autowired
private MockRestServiceServer server;
#TestConfiguration
static class SomeConfigFooBarBuzz {
#Bean
public SomeConfig provideConfig() {
return new SomeConfig(); // btw. not sure why this works,
// but this is the only way
// I got rid of the "unable to load
// SomeConfig auto-wire" or something like this :)
// Anyway not my main issue
// EDIT: just realized that the whole
// #TestConfiguration part can be avoided by
// adding SomeConfig.class to the classes in the
// #RestClientTest annotation
}
}
#Before
public void setUp() throws Exception {
server.expect(requestTo("/somePath")) // here an exception is thrown
// (main issue)
.andRespond(withSuccess("<valid json>", MediaType.APPLICATION_JSON));
}
}
The exception is very clear:
java.lang.IllegalStateException: Unable to use auto-configured
MockRestServiceServer since MockServerRestTemplateCustomizer has been bound to
more than one RestTemplate
But is it somehow possible to get this tested or is it not allowed to instantiate two different rest templates in one client class?
I have just the need to use the first rest template in some cases and the second one in some others.
After some days of investigation and communication with spring folks via GitHub I found a solution for me and not receiving an answer here means my solution might be valuable for someone:
#RunWith(SpringRunner.class)
#RestClientTest(RestClient.class)
public class RestClientTest {
#Autowired
private RestClient client;
private MockRestServiceServer firstServer;
private MockRestServiceServer secondServer;
private static MockServerRestTemplateCustomizer customizer;
#TestConfiguration
static class RestTemplateBuilderProvider {
#Bean
public RestTemplateBuilder provideBuilder() {
customizer = new MockServerRestTemplateCustomizer();
return new RestTemplateBuilder(customizer);
}
}
#Before
public void setUp() {
Map<RestTemplate, MockRestServiceServer> servers = customizer.getServers();
// iterate and assign the mock servers according to your own strategy logic
}
#Test
public void someTest() {
firstServer.expect(requestTo("/somePath"))
.andRespond(withSuccess("some json body"),
MediaType.APPLICATION_JSON));
// call client
// verify response
}
Basically specify a number of mock servers same as the number of rest templates you use in your client code, then specify a test configuration providing a rest builder with a customizer, so that your client code's rest templates will be built via this customized builder. Then use the customizer to get the mock servers bounded to the created rest templates and define expectations on them as you want.

Spring #PostConstruct depending on #Profile

I'd like to have multiple #PostConstruct annotated methods in one configuration class, that should be called dependent on the #Profile. You can imagine a code snipped like this:
#Configuration
public class SilentaConfiguration {
private static final Logger LOG = LoggerFactory.getLogger(SilentaConfiguration.class);
#Autowired
private Environment env;
#PostConstruct #Profile("test")
public void logImportantInfomationForTest() {
LOG.info("********** logImportantInfomationForTest");
}
#PostConstruct #Profile("development")
public void logImportantInfomationForDevelopment() {
LOG.info("********** logImportantInfomationForDevelopment");
}
}
However according to the javadoc of #PostConstruct I can only have one method annotated with this annotation. There is an open improvement for that in Spring's Jira https://jira.spring.io/browse/SPR-12433.
How do you solved this requirement? I can always split this configuration class into multiple classes, but maybe you have a better idea/solution.
BTW. The code above runs without problems, however both methods are called regardless of the profile settings.
I solved it with one class per #PostConstruct method. (This is Kotlin but it translates to Java almost 1:1.)
#SpringBootApplication
open class Backend {
#Configuration
#Profile("integration-test")
open class IntegrationTestPostConstruct {
#PostConstruct
fun postConstruct() {
// do stuff in integration tests
}
}
#Configuration
#Profile("test")
open class TestPostConstruct {
#PostConstruct
fun postConstruct() {
// do stuff in normal tests
}
}
}
You can check for profile with Environment within a single #PostContruct.
An if statement would do the trick.
Regards,
Daniel

Spring/TestNG integration: Auto-injection fails with two test classes

Hitting an odd issue with my spring based integration tests. Note that I'm not so much unit testing a spring IoC app as I'm using Spring to auto-inject properties into my test configuration. Also - we're not using any context configuration xml, everything is configured programatically.
I got the framework all set up and running with a single test before handing it off to the team tester, auto-injection was working fine. However, as soon as he added a second test class, auto-injection of my configuration field just stops working. I've cloned his branch and verified the behavior; Commenting out his class OR my original class causes the auto-injection to return to normal function.
Anybody ever seen this? Relevant code snippets below. I've tried moving around the #ContextConfiguration annotation but it didn't really help. It seems like Spring is having trouble deciding where the TestProperties object should come from. The field I've commented on below is coming up null when I run the tests, but only if there are two test classes enabled.
#ContextConfiguration(classes = TestConfig.class, loader = AnnotationConfigContextLoader.class)
public abstract class BaseIntegrationTest
extends AbstractTestNGSpringContextTests
{
#Autowired
protected MockServerClient mockServer;
// I get an error in IntelliJ Pro on this field.
// Could not autowire. There is more than one bean of 'TestProperties' type. Beans: getTestProperties, testProperties
// I've also tried #Qualifier("getTestProperties") and #Qualifier("testProperties")
// which both resolve the error in IntelliJ but the TestProperties field is still null when running the tests.
#Autowired
protected TestProperties properties;
//...
}
public class TestGetMuIT
extends BaseIntegrationTest
{
private ManagementUnitClient managementUnitClient;
#BeforeMethod(alwaysRun = true)
public void setup()
{
String endpoint = properties.getServiceBaseUri(); //NullPointerException thrown here during test run
}
}
Other relevant classes:
#Configuration
public class RootConfig
{
protected static final Logger LOGGER = LoggerFactory.getLogger(RootConfig.class);
private PropertyService propertyService;
private TestProperties testProperties;
#Bean
public PropertyService propertyService() throws IOException {
if (propertyService == null) {
propertyService = new DynamoDbPropertyService(config.getPropertiesConfig());
propertyService.initialize();
}
return propertyService;
}
#Bean
public TestProperties getTestProperties() throws IOException {
if (testProperties == null) {
testProperties = new TestProperties();
}
}
And the actual TestProperties class:
#Component
public class TestProperties
{
public static class Default {
public static final String BASE_URI = "http://localhost:8055";
}
#Autowired
private PropertyService propertyService;
public String getServiceBaseUri()
{
return propertyService.getPropertyRegistry().getString(TestConfigKey.LocalServiceBaseUri.key(), Default.BASE_URI);
}
}
And these are my spring config annotations:
#Configuration
//Note: TestProperties.class is a recent addition. I added that and commented out the #Bean getTestProperties() method in RootConfig as a troubleshooting step
#Import({RootConfig.class, TestProperties.class})
#EnableAspectJAutoProxy
#ComponentScan(basePackages = {"com.inin.wfm.it"},
excludeFilters = #ComponentScan.Filter(value = com.inin.wfm.it.config.ConfigPackageExcludeFilter.class, type = FilterType.CUSTOM))
public class TestConfig {\\... }
Again all of the above works just fine if there's only one test file (and it doesn't matter which of the two I comment out). Anybody know what I'm doing wrong in my configuration?

Categories

Resources