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.
Related
I can't understand why #Autowiring my #RestController class is returning null.
I want to do a basic unit test before doing an integrated test but its failing.
In fact anything that is being #Autowired is showing null in the test package.
I have a very simple test, I just want to see the basic works:
A very simple example:
#Component
public class SimpleComponent {
public int add(int a, int b){
return a + b;
}
}
And the test:
class SimpleComponentTest {
#Autowired
private SimpleComponent component;
#Test
public void givenSimpleComponentShouldNotBeNull(){
assertNotNull(component);//How on earth does this fail?
}
}
Here is the code from the Controller class:
#RestController
public class EmployeeAccountApi {
#PostMapping("/register")
public String register(){
return "Registering!";
}
}
public class EmployeeAccountApiUnitTest {
#Autowired
private EmployeeAccountApi employeeAccountApi;
#Test
public void testAutowiring(){
assertNotNull(employeeAccountApi);//Fails, Can't understand why
}
}
This works with the #Mock annotation:
But I dont want to mock it as that is the class I am unit testing.
I want to test that the method returns a basic string.
Question is why isn't it working?
#ExtendWith(MockitoExtension.class)
public class EmployeeAccountApiUnitTest {
#Mock
private EmployeeAccountApi employeeAccountApi;
#Test
public void testAutowiring(){
assertNotNull(employeeAccountApi);//This works
}
}
Even if I get the Employee Service that clearly worked and tested in the previous test also is null in this class:
public class EmployeeAccountApiUnitTest {
#Autowired
private EmployeeAccountApi employeeAccountApi;
#Autowired
private EmployeeService service;
#Test
public void testAutowiring(){
//assertNotNull(employeeAccountApi);
assertNotNull(service);
}
}
Why is #Autowired null in the Test?
Mocking is working fine
Plenty of similar questions but they are too specific and not provide any basic information
#Autowired only works if you are spinning up the application context for your application. Annotate the test class with #SpringBootTest, which tells the test to start the entire spring application context, and it should work.
#SpringBootTest has to be used on the test class for Spring to load your ApplicationContext and wire things up. Without out this there's nothing for Spring to inject.
You can use #WebMvcTest on the test class to focus your tests only on the Web Layer.
There's a whole bunch of annotations that allow you to "slice" your ApplicationContext up lots of other ways for testing various other parts of your application.
Spring's documentation and tutorials are really very good. You should check them out.
Testing the Web Layer
I have a spring boot project where I'm trying to mock my repository for tests.
I would like my repository.count() to return 30L but it actually always return 0
#Service
#Transactional
public class DishServiceImpl implements DishService {
private final DishRepository dishRepository;
public DishServiceImpl(DishRepository dishRepository) {
this.dishRepository = dishRepository;
}
#Override
public List<Dish> searchDishes() {
long countDish = dishRepository.count();
System.out.println(countDish);
[...]
}
}
#RunWith(SpringRunner.class)
#SpringBootTest(classes = WhatAreWeEatingApp.class)
#Transactional
public class DishServiceTest{
#Mock
private DishRepository dishRepository;
#Autowired
private DishService dishService;
#Test
public void test(){
when(dishRepository.count()).thenReturn(30L);
dishService.searchDishes();
[...]
}
You repository mock is never set as dependency to the bean service.
Here you mock in the frame of a running Spring container :
#RunWith(SpringRunner.class)
#SpringBootTest(classes = WhatAreWeEatingApp.class)
It is not unit test. So you want to use #MockBean from Spring Boot to mock a bean in the container, not #Mock from Mockito to mock instances created outside the container.
Don't like auto promotional post but this question should help you.
To go further you should not need to run a container to test the service method. So you should probably remove the Spring Boot test annotation and write a real unit test.
I have an application built with JHipster which contains several tests.
I created a simple configuration class that instantiate a bean connected to an external service as such :
#Configuration
public class KurentoConfiguration {
#Bean(name = "kurentoClient")
public KurentoClient getKurentoClient(#Autowired ApplicationProperties applicationProperties) {
return KurentoClient.create(applicationProperties.getKurento().getWsUrl());
}
}
But as you would guess, this code crash during testing because the external service is not up but this code is still run during application context loading.
So I need to create a "stateless" version of this bean to be used during testing.
Here is a simple example of a test that fail because of my configuration :
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Face2FaceApp.class)
public class LogsResourceIntTest {
private MockMvc restLogsMockMvc;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
LogsResource logsResource = new LogsResource();
this.restLogsMockMvc = MockMvcBuilders
.standaloneSetup(logsResource)
.build();
}
#Test
public void getAllLogs()throws Exception {
restLogsMockMvc.perform(get("/management/logs"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE));
}
}
What is the solution to make this bean not highly dependent of an external service during unit testing ?
You can use the MockBean annotation in your test to replace your existing bean :
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Face2FaceApp.class)
public class LogsResourceIntTest {
#MockBean
private KurentoClient kurentoClient;
private MockMvc restLogsMockMvc;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
LogsResource logsResource = new LogsResource();
this.restLogsMockMvc = MockMvcBuilders
.standaloneSetup(logsResource)
.build();
given(kurentoClient.someCall()).willReturn("mock");
}
....
}
Here is the Spring Boot documentation :
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications-mocking-beans
Thanks to the help of everyone here, I managed to solve this problem :
I created an interface of KurentoClient and implemented a proxy that call KurentoClient methods
My "normal" #Bean kurentoClient returns the implemented proxy
I writed a #TestConfiguration (UnitTestConfiguration) and added a #Bean with the same signature as the one crated above but this one returns mockito's mock(KurentoClient.class)
I created a class TestBase that every test class extends and which
contains
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyApp.class)
#ContextConfiguration(classes = UnitTestConfiguration.class)
public class TestBase {
}
I have this test:
#RunWith(MockitoJUnitRunner.class)
public class myServiceTest {
#InjectMocks
myService subject;
private myService spy;
#Before
public void before() {
spy = spy(subject);
}
#Test
public void testing() {
when(spy.print2()).thenThrow(new RuntimeException()).thenThrow(new RuntimeException()).thenReturn("completed");
spy.print1();
verify(spy, times(3)).print2();
}
and then I have:
#Service("myService")
public class myService extends myAbstractServiceClass {
public String print1() {
String temp = "";
temp = print2();
return temp;
}
#Retryable
public String print2() {
return "completed";
}
}
then I have this interface(which my abstractService implements):
public interface myServiceInterface {
#Retryable(maxAttempts = 3)
String print1() throws RuntimeException;
#Retryable(maxAttempts = 3)
String print2() throws RuntimeException;
}
but, I get a runtimeexception thrown when I run the test, leading me to believe it is not retrying. Am I doing this wrong?
This is because you are not using the SpringJUnitClassRunner.
Mockito and your own classes are not taking the #Retryable annotation in account. So you rely on the implementation of Spring to do so. But your test does not activate Spring.
This is from the SpringJUnit4ClassRunner JavaDoc:
SpringJUnit4ClassRunner is a custom extension of JUnit's BlockJUnit4ClassRunner which provides functionality of the Spring TestContext Framework to standard JUnit tests by means of the TestContextManager and associated support classes and annotations.
To use this class, simply annotate a JUnit 4 based test class with #RunWith(SpringJUnit4ClassRunner.class) or #RunWith(SpringRunner.class).
You should restructure your test class at least to something like:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=MyConfig.class)
public class MyServiceTest {
#Configuration
#EnableRetry
#Import(myService.class)
public static class MyConfig {}
...
What am I doing there?
activate the Spring JUnit hook
specify the Spring context configuration class
define the spring configuration and import your service as a bean
enable the retryable annotation
Are there some other pitfalls?
Yes, you are using Mockito to simulate an exception. If you want to test this behaviour with Spring like this, you should have a look at Springockito Annotations.
But be aware of that: Springockito you will replace the spring bean completely which forces you to proxy the call of your retryable. You need a structure like: test -> retryableService -> exceptionThrowingBean. Then you can use Springockito or what ever you like e.g. ReflectionTestUtils to configure the exceptionThrowingBean with the behaviour you like.
You should reference the interface type of your service in your test: MyServiceInterface
And last but not least. There is a naming convention nearly all Java developers follow: class names have first letter of each internal word capitalized
Hope that helps.
Another way:
#EnableRetry
#RunWith(SpringRunner.class)
#SpringBootTest(classes={ServiceToTest.class})
public class RetryableTest {
#Autowired
private ServiceToTest serviceToTest;
#MockBean
private ComponentInsideTestClass componentInsideTestClass;
#Test
public void retryableTest(){
serviceToTest.method();
}
}
I think you should let Spring manage the bean, create the appropriate proxy and handle the process.
If you want to mock specific beans, you can create mocks and inject them to the service under test.
1st option could be unwrapping proxied service, creating mocks and manually injecting them:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {RetryConfiguration.class})
#DirtiesContext
public class TheServiceImplTest {
#Autowired
private TheService theService;
#Before
public void setUp(){
TheService serviceWithoutProxy = AopTestUtils.getUltimateTargetObject(theService);
RetryProperties mockRetryProperties = Mockito.mock(RetryProperties.class);
ReflectionTestUtils.setField(serviceWithoutProxy, "retryProperties", mockRetryProperties);
}
#Test
public void shouldFetch() {
Assert.assertNotNull(theService);
}
}
In this example, I mocked one bean, RetryProperties, and injected into the service. Also note that, in this approach you are modifying the test application context which is cached by Spring. This means that if you don't use #DirtiesContext, service will continue its way with mocked bean in other tests. You can read more here
Second option would be creating a test specific #Configuration and mock the depended bean there. Spring will pick up this new mocked bean instead of the original one:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {RetryConfiguration.class, TheServiceImplSecondTest.TestConfiguration.class})
public class TheServiceImplSecondTest {
#Autowired
private TheService theService;
#Test
public void shouldFetch() {
Assert.assertNotNull(theService);
}
#Configuration
static class TestConfiguration {
#Bean
public RetryProperties retryProperties() {
return Mockito.mock(RetryProperties.class);
}
}
}
In this example, we have defined a test specific configuration and added it to the #ContextConfiguration.
I am looking for the way to add embedded elasticsearch to my spring boot integration test.
I looked at elastic search integration test but it does not work together with spring boot as both should uses different test runner.
I have a class test as below unfortunately it does not work with error:
java.lang.IllegalStateException: No context information for thread:
Thread[id=1, name=main, state=RUNNABLE, group=main]. Is this thread
running under a class
com.carrotsearch.randomizedtesting.RandomizedRunner runner context?
Add #RunWith(class
com.carrotsearch.randomizedtesting.RandomizedRunner.class) to your
test class. Make sure your code accesses random contexts within
#BeforeClass and #AfterClass boundary (for example, static test class
initializers are not permitted to access random contexts).
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = App.class)
#WebAppConfiguration
#IntegrationTest("server.port:0")
public class TestExample extends ElasticsearchIntegrationTest {
TestRestTemplate testRestTemplate = new TestRestTemplate();
#Value("${local.server.port}")
int port;
#Test
public void testOne(){
ResponseEntity<String> results = testRestTemplate.getForEntity(String.format("http://localhost:%d/client/1", port), String.class);
System.out.print(results);
}
}
Does anybody has some ideas how to make them run or what is alternatives ??
You can actually do what you need without any additional elasticsearch testing dependencies. The idea is basically to create an embedded node and then use the NodeClient to communicate with it.
For that, I created my own EmbeddedElasticsearchServer class which looks (more or less) like this:
public class EmbeddedElasticsearchServer implements InitializingBean {
public EmbeddedElasticsearchServer() {
ImmutableSettings.Builder elasticsearchSettings = ImmutableSettings.settingsBuilder()
.put("http.enabled", "false")
.put("path.data", "target/elasticsearch-data");
node = nodeBuilder()
.local(true)
.settings(elasticsearchSettings.build())
.node();
client = node.client();
}
#Override
public void afterPropertiesSet() throws Exception {
// Initialization stuff:
// - create required indices
// - define mappings
// - populate with test data
}
public Client getClient() {
return client;
}
}
Then, in spring configuration (let's call it integration-test-context.xml) I did this:
<bean id="embeddedElasticsearchServer"
class="com.example.EmbeddedElasticsearchServer" />
<bean id="elasticsearchClient"
class="org.elasticsearch.client.node.NodeClient"
factory-bean="embeddedElasticsearchServer"
factory-method="getClient" />
Then you can just autowire the client in your test like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("/integration-test-context.xml")
public abstract class AbstractElasticsearchIntegrationTest {
#Autowired
private Client elasticsearchClient;
// Your rests go here...
}