I'm trying to test if my spring boot application correctly receives messages from an AWS SQS queue by using spring clouds aws messaging.
Listener:
#Component
public class RequestListener {
private static final Logger LOG = LoggerFactory.getLogger(RequestListener.class);
private final MyService myService;
public RequestListener(MyService myService) {
this.myService = myService;
}
#SqsListener(value = "${aws.queue.url}", deletionPolicy = SqsMessageDeletionPolicy.ALWAYS)
void getRequest(MyRequest request) {
LOG.info("Fetched new message from SQS: {}", request);
myService.processRequest(request);
}
}
Test:
#ExtendWith(SpringExtension.class)
#ActiveProfiles(SpringProfiles.TEST)
#SpringBootTest(webEnvironment = RANDOM_PORT)
class TestClass {
...
#MockBean
MyService myService;
#Test
void testReceiveMessage() {
var payload = MyRequest.builder().content("foo").build();
messagingTemplate.convertAndSend(testQueueUrl, payload);
when(myService.processRequest(payload)).thenReturn(true);
verify(myService, Mockito.timeout(2000).atLeastOnce()).processRequest(payload);
}
...
The message is received everytime (I see the log) but there's a 50% chance that the verify will fail as the Mock is never called (Error: zero interactions). I first thought this might be some synchronization issue (thus the timeout) but I can actually see that the real MyService service is called and not the Mock whenever the test fails.
I run the tests using mvn clean install
So my question is, how is it possible that randomly somtimes the Mock and sometimes the real object is called?
I also tried deactivating the real service with a custom #Profile annotation for the test but that didn't change the behaviour either.
Tested with Boot-Version 2.3.9.RELEASE and spring cloud HOXTON.SR10
(and also with Boot-Version 2.4.1 and cloud version HOXTON.SR10)
Thanks for any hints
Related
I'm trying to write a unit test for a method in the service layer of a WebFlux application which processes the response of another service that makes a REST call and returns a Mono of the result.
I'm already testing the nested service using WebTestClient in it's own unit tests so am trying to mock out the response from it using Mockito but am encountering a NullPointerException as if the result isn't being mocked.
I'm relatively new to async/reactive patterns so not sure if I'm doing this wrong or if it's Mockito not playing nicely with the async nature of react however it works fine if it's not a nested service call that is being mocked?
I've replicated it in a minimal example that show's it's purely the mocking of the nested service which isn't working as expected, where NestedService.doRestCall() returns a Mono<String>:
#Service
public class ExampleService {
private final NestedService nestedService;
#Autowired
public ExampleService(final NestedService nestedService) {
this.nestedService = nestedService;
}
public Mono<String> methodToTest() {
return nestedService.doRestCall()
.map(data -> data + "-test");
}
}
And the test:
#RunWith(SpringRunner.class)
#SpringBootTest
public class ExampleServiceTests {
#Mock
private NestedService nestedServiceMock;
#InjectMocks
private ExampleService exampleService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void test() {
when(nestedServiceMock.doRestCall()).thenReturn(Mono.just("foo"));
StepVerifier.create(exampleService.methodToTest())
.expect("foo-test")
.verifyComplete();
}
}
I get the following NullPointerException trace triggered from the .map() call in the ExampleService line, I've also tried using flatMap() but get the same error:
java.lang.NullPointerException
at com.example.service.ExampleService.methodToTest(ExampleService.java:18)
at com.example.service.ExampleServiceTests.test(ExampleServiceTests.java:33)
You initialize your mocks twice:
first by spring runner
then by MockitoAnnotations.initMocks(this);
Lets inspect the variables in the setup method with debugger:
Before initMocks:
nestedServiceMock = {NestedService$MockitoMock$267376363#5567} "nestedServiceMock"
exampleService = {ExampleService#5568}
After initMocks:
nestedServiceMock = {NestedService$MockitoMock$267376363#5661} "nestedServiceMock"
exampleService = {ExampleService#5568}
exampleService.nestedServiceMock = {NestedService$MockitoMock$267376363#5567} "nestedServiceMock"
You can clearly see that the nestedServiceMock was re-initialized, but the exampleService was not, and still holds reference to the old nested object.
To solve, get rid of Spring boot annotations.
Here, cleaned-up version with JUnit5:
#ExtendWith(MockitoExtension.class)
public class ExampleServiceTest {
#Mock
private NestedService nestedServiceMock;
#InjectMocks
private ExampleService exampleService;
#Test
public void test() {
when(nestedServiceMock.doRestCall()).thenReturn(Mono.just("foo"));
StepVerifier.create(exampleService.methodToTest())
.expectNext("foo-test")
.verifyComplete();
}
}
Bit of a weird one that I've been scratching my head over for the past few days. I have a JPA repository that is field injected into a service class. Is works perfectly when running the server and sending a request via a client but when the code is executed via integration tests the field injected class (CustomerRepository ) is always null.
I've tried various advice via the internet but I've not found a similar scenario to mine, any help would be much appreciated
Service class
#GRpcService
public class CustomerService extends CustomerServiceGrpc.CustomerServiceImplBase {
#Autowired
private CustomerRepository repository;
#Override
public void createCustomer(CreateCustomerRequest request, StreamObserver<CreateCustomerResponse> responseObserver) {
final CustomerDao convertedDao = ProtoToDaoConverter.convertCustomerRequestProtoToCustomerDao(request);
repository.save(convertedDao);
responseObserver.onNext(CreateCustomerResponse.newBuilder().setSuccess(true).build());
responseObserver.onCompleted();
}
}
Integration test
#ExtendWith(SpringExtension.class)
#SpringBootTest
public class CustomerServiceIT {
#Rule
private final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
#Test
public void something() throws IOException {
String serverName = InProcessServerBuilder.generateName();
// Create a server, add service, start, and register for automatic graceful shutdown.
grpcCleanup.register(InProcessServerBuilder
.forName(serverName).directExecutor().addService(new CustomerService()).build().start());
customerServiceGrpc.CustomerServiceBlockingStub blockingStub = CustomerServiceGrpc.newBlockingStub(
// Create a client channel and register for automatic graceful shutdown.
grpcCleanup.register(InProcessChannelBuilder.forName(serverName).directExecutor().build()));
final CreateCustomerRequest request = CreateCustomerRequest.newBuilder().setFirstName("Simon").setSecondName("Brown").setRole("Product Developer").build();
final CreateCustomerResponse response = blockingStub.createCustomer(request);
}
}
In test you invoke new CustomerService(). You create an object by itself, not via spring. I guess you should create a field in test class
#Autowired private final CustomerService customerService
and pass it in
grpcCleanup.register(InProcessServerBuilder
.forName(serverName).directExecutor().addService(customerService).build().start());
You can use Mockito or any other tests framework to mock your class dependencies (your service and your JPA Repository).
You can use these features :
#InjectMocks - Instantiates testing object instance and tries to inject fields annotated with #Mock or #Spy into private fields of testing object
#Mock - Creates mock instance of the field it annotates
In your test class your have to use #Mock to inject "a fake respository" :
#ExtendWith(SpringExtension.class)
#SpringBootTest
public class CustomerServiceTest {
#InjectMocks
private CustomerService testingObject;
#Mock
private CustomerRepository customRepository;
#Rule
private final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
#BeforeMethod
public void initMocks(){
MockitoAnnotations.initMocks(this);
}
#Test
public void something() throws IOException {
// inside the testing method you have to define what you mocked object should return.
// it means mocking the CustomRepository methods
CustomerDao customer = new CustomerDao(); // fill it with data
Mockito.when(customRepository.save()).thenReturn(customer);
// Create a server, add service, start, and register for automatic graceful shutdown.
grpcCleanup.register(InProcessServerBuilder
.forName(serverName).directExecutor().addService(new CustomerService()).build().start());
customerServiceGrpc.CustomerServiceBlockingStub blockingStub = CustomerServiceGrpc.newBlockingStub(
// Create a client channel and register for automatic graceful shutdown.
grpcCleanup.register(InProcessChannelBuilder.forName(serverName).directExecutor().build()));
final CreateCustomerRequest request = CreateCustomerRequest.newBuilder().setFirstName("Simon").setSecondName("Brown").setRole("Product Developer").build();
final CreateCustomerResponse response = blockingStub.createCustomer(request);
}
}
As your repository and it's methods are mock you customerService should be populated with fake data for test purpose.
Note : : It nice to have a naming convention for classes and especially tests classes. A common use is to always give the a suffix of xxTest as i did in the answer
It's hard to answer without being able to read stacktraces. I would start investigating this problem by looking at the spring context configuration classes. Maybe some of them are missing at the runtime of your integration test.
If spring #Configuration or other #Component classes exist in your application, it might help to load them explicitly with your tests, along with your unexpectedly null-value CustomerRepository:
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = {AppConfig.class, CustomerRepository.class })
public class CustomerServiceIT {
This might not solve it, but maybe reveals some error messages that help you to investigate the problem.
I created with java spring-boot a server (“node”). There are a few instance of nodes, as the difference is by a configuration file for every node. For example:
node1.properties:
application.name=FullNode
receiving.server.addresses=tcp://localhost:8001
propagation.server.addresses=tcp://localhost:8002
recovery.server.address=http://localhost:8060
....
node 2.properties:
application.name=FullNode
receiving.server.addresses=tcp://localhost:6001
propagation.server.addresses=tcp://localhost:6002
recovery.server.address=http://localhost:8050
...
To test the process of sending data to the server, I wrote a JUnit test for the TransactionController.
TransactionController:
#RestController
#RequestMapping("/transaction")
public class TransactionController {
#Autowired
private TransactionService transactionService;
...
#RequestMapping(method = PUT)
public ResponseEntity<Response> addTransaction(#Valid #RequestBody
AddTransactionRequest addTransactionRequest) {
return transactionService.addNewTransaction(addTransactionRequest);
}
...
}
Test:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = AppConfig.class)
#SpringBootTest
public class DBTests {
#Autowired
private TransactionController transactionController;
#Test
public void addTransaction() {
transactionController.addTransaction(transactionRequest);
}
}
The problem is, that every node also sends his transactions to the other nodes. But How I could test it with JUnit? I could not just create in the test two instance of a TransactionController, because TransactionController is a spring bean singleton, and the only way to run a node, is with his configuration file.
How could I do it?
I would look at the Mockito framework. Mocking out the TransactionService. Instead of firing up 2 servers which lends itself to be more of an integration test instead of a unit test.
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.
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...
}