Unit test Spring Boot for two beans that implement a service interface in one test class - java

On my service layer of Spring Boot web application I have a service interface with several methods and two implementing beans.
interface VehicleService { }
#Service
class CarServiceImpl implements VehicleService {}
#Service
class BycicleServiceImpl implements VehicleService {}
I have generated unit test for one of the services as so
#RunWith(SpringRunner.class)
public class CarServiceTest {
#TestConfiguration
static class VehicleServiceTestContextConfiguration {
#Bean
public VehicleService vehicleService() {
return new CarServiceImpl();
}
}
#Autowired
private VehicleService vehicleService;
#Test
public void getCar() throws Exception {
//tests here with CarServiceImpl object
}
}
When I want to run tests BicycleServiceImpl bean I will have to copy and paste same class and change #Bean to return BicycleServiceImpl which doesn't seem to be best solution. Is there any way I can run same test class twice and inject each bean one by one. Any advice would be greatly appreciated.

Related

Implementation method not being called from Rest Controller using Spring

I'm trying to call an implementation method from my controller class. I've annotated the interface with #Component and Autowired this interface in my controller. But, its throwing me 404 exception.
On another way, I created a simple DAO and annotated with #Component, this is working from my controller.
My question is I want to follow by calling an interface method, which inturn will call the DAO.
Here is my flow.
#RestController
public class PurchaseController {
/*#Autowired
private DpDAO dpDAO;*/ ----> This is working
#Autowired
private PurchaseService purchaseService; --> This is not working
#GetMapping("/purchase/{partyId}/{dealId}")
public String createPurchase(#PathVariable("partyId") String partyId, #PathVariable("transactionId") String transactionId) {
return purchaseService.createPurchase(partyId, transactionId); --> This is not working
//return dpDAO.createPurchase(partyId, transactionId); --> This is working
}
}
My interface
#Component
public interface PurchaseService {
public String createPurchase(String partyId, String transactionId);
}
DpDAO class
#Component
public class DpDAO {
public String createPurchase(String partyId, String dealId) {
// Able to get logs here
return null;
}
}
Cannot we annotate the interfaces? Any ideas would be greatly appreciated.
I suspect the issue is that PurchaseService is an interface, while DpDAO is a class. That is, you have an instance of the latter but not of the former.
If that's true, then Spring cannot find a bean to inject. So you'll need some way of creating an instance of PurchaseService to be injected.
You could create an instance thus:
#Component
class PurchaseServiceImpl implements PurchaseService
{
// Fill in
}
Or you have a factory method on one of your initialiser classes. Something like:
#Bean
public PurchaseService createService()
{
return new PurchaseServiceImpl();
}
Your interfaces is a specification. Although you annotate with #Autowired your Interface what spring really does is injecting the implementation you specified.
Thus
either you provide an interface implementation as a component
#Service
public class PurchaseServiceImpl implements PurchaseService {
}
Or you create a bean that gives back the implementation of your choice
#Configuration
public class MyConfig {
#Bean
public PurchaseService purchaseService() {
//TODO
}
}
There are case of multiple interface implementations. In those cases you use the qualifier. For example
#Service("one")
public class PurchaseServiceIOne implements PurchaseService {
}
#Service("two")
public class PurchaseServiceTwo implements PurchaseService {
}
The you inject the service using the qualifier
#Autowired
#Qualifier("one")
private PurchaseService purchaseService;

Excluding an ApplicationListener #Component in Spring Boot during tests

I am trying to have my test unit up and running, and I have encountered a weird issue. My application uses an ApplicationListener class annotated as a #Component to perform an operation during startup.
During tests I have mocked the service that contains the logic, but I found that even though Mockito's when instructions work well in controller scope, the bean is not initialized for this ApplicationListener class: instead of returning what I define in the test unit, it returns either false or null - depending on the data type returned by each method in the service.
Since I have not found any way to initialize the mocked service from the test unit for the ApplicationListener class, I have decided to exclude it. To do so I have tried different approaches, being the one most often used that of creating a test application context and change its configuration. Unfortunately, nothing I have seen is working - so I am here asking for help. If possible, I would prefer not touching the ApplicationListener class and do all related coding in the test code.
I am interested in any of the two possible solutions, if they can be done:
1.- Get the mocked behaviour during the ApplicationListener execution, but I have read somewhere that this cannot be done
2.- Exclude the #Component from the test unit somehow.
TestUnit.Java:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = TestApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
public class TestConfigurationService {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#MockBean
private MockService mockService;
private void initMockBean () throws Exception {
when(mockService.isDoingSomething()).thenReturn(true);
}
#Before
public void setup() throws Exception {
// Spring mock context application setup
this.mockMvc = webAppContextSetup(webApplicationContext).build();
// Initialize ConsulService mock bean
initMockBean ();
}
}
TestApplication.java
#SpringBootApplication
#EnableAutoConfiguration
#ComponentScan(basePackages="my.base.package", excludeFilters = #Filter(type = FilterType.ASSIGNABLE_TYPE, classes = StartupConfiguration.class))
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
Besides what is shown in the code, I have also tried this annotation in file TestApplication.java:
#SpringBootApplication(exclude={StartupConfiguration.class})
StartupConfiguration.java
#Component
public class StartupConfiguration implements ApplicationListener<ContextRefreshedEvent> {
#Autowired
private ConfigurationService configurationService;
#Override
public void onApplicationEvent(final ContextRefreshedEvent event) {
try {
configurationService.updateConfiguration();
} catch (Exception e) {
throw new RuntimeException ("Error", e);
}
}
}
ConfigurationService.java
public interface ConfigurationService {
public void updateConfiguration () throws Exception;
}
ConfigurationServiceImpl.java
#Service
#Transactional
public class ConfigurationServiceImpl implements ConfigurationService {
#Autowired
private MService mockService;
#Override
public void updateConfiguration() throws Exception {
if (mockService.isDoingSomething()==false)
throw new Exception ("Something went wrong");
}
}
Versions:
Spring Boot 1.5.4.RELEASE,
Java 1.8
You can create mock bean of the same type and mark it with #Primary annotation to replace real bean. You can achieve this by having test such configuration:
#Configuration
#Import(TestApplication.class)
public class TestConfiguration {
#Bean
#Primary
public ConfigurationService configurationService() {
return Mockito.mock(ConfigurationService.class);
}
}
then get this mock in test:
...
public class TestConfigurationService {
...
#Autowired
ConfigurationService configurationService;
#Before
public void setUp() {
when(mockService.isDoingSomething()).thenReturn(true);
}
}
Thanks, araxn1d. Your answer gave me the clue to solve this issue.
I mocked the StartupConfiguration class in TestUnit.java:
#MockBean
private StartupConfiguration startupConfiguration;
Though in this case I was lucky: application listeners don't have returning methods, so they don't need when test configuration. If I had required that some method there returned for example true or a value, this method would not apply.
But at least for application listeners, this is enough.

Testing Spring boot REST resource issues

There is a microservice made with Spring Boot. It consist of Jetty, Jersey, Jackson and Liquibase. One of tasks of this service is to receive some data via REST and return response. This operation goes through next units:
MyResource, #Component REST with JAX-RS annotations, that receives data and ask #Autowired MyService for response.
MyService, #Component service with #Autowired MyResource to ask it for response.
MyResource, simple #JpaRepository interface
This application works fine, but now I need to add some tests for every module. Usually I use Mockito to test units, so I test my service with mocked MyRepository (Mockito #Mock annotation + when() method). I want to test MyResource.java the same way.
I tried to use TestRestTemplate way to test with spring-boot-starter-test and my test class looks like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebIntegrationTest(randomPort = true)
public class MyResourceTest {
#Value("${local.server.port}")
private int port;
private String getBaseUrl() {
return "http://localhost:" + port;
}
#Test
public void test() {
final TestRestTemplate restTemplate = new TestRestTemplate();
assertEquals(restTemplate.postForEntity(getBaseUrl() + "/test", null, ResponseObject.class).getBody(), new ResponseObject());
}
}
And there are two problems. First - when my test is running, they run up whole spring application, so my liquibase scripts is trying to find database and this is a very long-time process. Second - I can't replace MyService class with Mockito proxy.
I tried to find some manuals about best practices in testing spring boot REST applications and I found MockMvc-based way, but it looks like don't run up server to run test. Can you please share your way to test REST resource in spring boot?
MockMvc is the prefered solution for your problem.
It runs the spring boot application but 'mocks' the request,so it does not really run over http but behaves as such.
You can get all beans of your application injected into your test class using #Autowired, so you can mock spring beans there.
I run all my tests over 6 classes of configuration/support.
AbstractTest to configure core of tests
#ActiveProfiles(resolver = TestActiveProfilesResolver.class)
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#IntegrationTest
#SpringApplicationConfiguration(classes = Application.class)
public abstract class AbstractTest {
...
}
AbstractRepositoryTest to test my repositories over jdbc+jdbi
#Transactional
public abstract class AbstractRepositoryTest<R> extends AbstractTest implements Repositories {
#Inject
private ObjectMapper mapper;
#Inject
protected Repositories repositories;
private final Class<R> repositoryType;
...
}
AbstractServiceTest to test my services, this class contains the core of my service's test
public abstract class AbstractServiceTest {
...
}
AbstractIntegrationTest contains the core of my integration tests, utils methods to test controller's, etc.
public abstract class AbstractIntegrationTest extends AbstractTest {
...
}
Application provides to AbstractTest a context to run tests, this classe is same class that I use to run my spring boot application
#SpringBootApplication
public class Application extends SpringBootServletInitializer {
public static void main(final String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
protected SpringApplicationBuilder configure(final SpringApplicationBuilder application) {
return application.sources(Application.class);
}
...
}
And finnaly the TestActiveProfilesResolver, that provide the profile to match application-test.properties, It's necessary because exist the open issue on JIRA, here
public class TestActiveProfilesResolver implements ActiveProfilesResolver {
#Override
public String[] resolve(final Class<?> testClass) {
final String activeProfile = System.getProperty("spring.profiles.active");
return new String[] {activeProfile == null ? "test" : activeProfile};
}
}
Sometimes my tests extend AbstractRepositoryTest, AbstractIntegrationTests or AbstractServiceTest, it depends on what I want.
This configuration solved all my problems to test services, controllers etc.

Not able to use spring dependency injection across various classes for TestNG using java annotations(JSR 330 Standard Annotations)

I have a service which I am trying to inject across various classes in my tests but I am getting its instance as null.
My config interface:
MyService.java
public interface MyService {
public String getHostUri();
}
My implementation class of this interface: MyServiceImpl.java
public class MyServiceImpl implements MyService {
private static final String BASE_HOST_URI_CONFIG = "localhost:4444";
#Override
public String getHostUri() {
return BASE_HOST_URI_CONFIG;
}
My Spring configuration class with the bean:
#Configuration
public class AutomationSpringConfig {
#Bean
public MyService getMyService(){
return new MyServiceImpl();
}
}
My testNG class:
#ContextConfiguration(classes=AutomationSpringConfig.class ,loader =AnnotationConfigContextLoader.class)
public class BasicAutomatedTest extends AbstractTestNGSpringContextTests {
private static final Logger LOGGER = Logger.getLogger(BasicAutomatedTest.class);
#Inject
private MyService myService;
#Test
public void basicTest {
Setup setup = new Setup();
LOGGER.info(myService.getHostUri());
LOGGER.info(setup.myService.getHostUri());
}
}
My helper class in which I am not able to get the injection:
public class Setup {
#Inject
public MyService myService;
}
So when I try to get the hostUri via the setup object in the BasicAutomatedTest's basicTest method I get a NullPointerException.
So I am not able to inject the MyService bean in the Setup class.
In order to use annotations you need to specify that behaviour in your beans XML configuration file. Something like this:
<context:component-scan base-package="your.base.package"/>
<context:annotation-config/>
Hope it helps!

reduce spring ContextConfiguration boilerplate in tests

I was wondering if there is a way to reduce the amount of boilerplate that we are currently writing for out integration tests.
The main culprit is ContextConfiguration that we send 7 distinct strings into currently.
One of our tests looks like this (payload code removed):
#ContextConfiguration(locations = {"classpath:properties-config.xml",
"classpath:dataSources-config.xml",
"classpath:dao-config.xml",
"classpath:services-config.xml",
"classpath:ehcache-config.xml",
"classpath:test-config.xml",
"classpath:quartz-services.xml"})
#RunWith(SpringJUnit4ClassRunner.class)
#Category(IntegrationTest.class)
public class TerminalBuntsPDFTest {
#Autowired
private JobService jobService;
#Test
public void testCode() throws SystemException {
assertTrue("Success", true);
}
}
And the specification of what xml files to load takes up a lot of space. We are in a (very slow) process of migrating away from xml towards annotations, but there is a lot of work left to do in that project.
We are using spring 3.2.
The annotation based approach is to create a Spring Configuration Java class like this:
#Configuration("testConfig")
#ImportResource({
"dataSources-config.xml",
"dao-config.xml",
"services-config.xml"
})
public class TestConfiguration {
// TO create a spring managed bean
#Bean
MyBean myBean() {
return new MyBean();
}
}
Then you can annotate your test class like so to load the configuration:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(
classes=TestConfiguration.class,
loader=AnnotationConfigContextLoader.class
)
#Category(IntegrationTest.class)
public class TerminalBuntsPDFTest {
This is just a light example that probably won't compile but should get you on the right track
Some relevant docs:
http://www.tutorialspoint.com/spring/spring_java_based_configuration.htm
http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html
What about such pattern:
#ContextConfiguration(locations = {"classpath:properties-config.xml",
"classpath:dataSources-config.xml",
"classpath:dao-config.xml",
"classpath:services-config.xml",
"classpath:ehcache-config.xml",
"classpath:test-config.xml",
"classpath:quartz-services.xml"})
#RunWith(SpringJUnit4ClassRunner.class)
#Category(IntegrationTest.class)
public abstract class BaseTest {
}
// ....
public class TerminalBuntsPDFTest extends BaseTest {
#Autowired
private JobService jobService;
#Test
public void testCode() throws SystemException {
assertTrue("Success", true);
}
}
// ....
public class TerminalBuntsPDFTest2 extends BaseTest {}
This will allow you to place configuration only once in parent abstract class.

Resources