How to test a Spring REST consumer with JUnit - java

I'm creating a Java Spring based microservice application that communicates using REST endpoints.
The app itself is so far has a simple structure: UI <-> DBLayer. The UI module is a api consumer and DBLayer is an api provider.
That being said I would like to test if my UI makes the correct REST calls using JUnit and/or Mockito. To be more specific, say I have a service class like this:
#Service
public class AuthorityService {
#Autowired
private RestTemplate restTemplate;
public Authority getAuthority(String authorityName) {
Authority authority =
restTemplate.getForObject(
"http://localhost:8080/authorities/" + authorityName,
Authority.class);
return authority;
}
}
In order to test this service method I would like to somehow verify that exactly this endpoint was called. Is there a way to wrap the service method and somehow assert a rest GET/POST/PUT etc. calls being made?
The desired test class should look something like this:
public class AuthorityServiceTest {
private AuthorityService authorityService = new AuthorityService();
#Test
public void getAuthorityTest(){
Assert.assertHttpGETCallMade(
authorityService.getAuthority("test"),
"http://localhost:8080/authorities/test");
}
}

You can use Mockito to inject the template, then verify the call.
#ExtendWith(MockitoExtension.class) // RunWith(MockitoJUnitRunner.class) for JUnit 4
public class AuthorityServiceTest {
#InjectMocks
private AuthorityService sut;
#Mock RestTemplate restTemplate;
#Test
public void getAuthorityTest(){
// mock rest call
Authority auth = mock(Authority.class);
when(restTemplate.getForObject(any(String.class), any(Class.class)).thenReturn(auth);
Authority result = sut.getAuthority("test");
// verify mock result was returned
assertSame(auth, result);
// verify call to rest template was performed
verify(restTemplate).getForObject(
"http://localhost:8080/authorities/test",
Authority.class);
}
}

Related

Java writing Spring Boot Unit Tests

I was wondering if I wrote this test in a proper way to mock a real situation. Can you please provide any feedback?
I'm using Mockito in a Spring Boot environment. I'm new to Mockito and other mocking techniques, but I want to know if I'm on the right path or not.
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest
class FileServiceImplTest {
#Mock
private UserPrincipal userPrincipal;
#Mock
private User user;
#Mock
private FileService fileService;
#BeforeEach
void initialize() {
user = new User("testUser", "test#email.com", "testPassword");
user.setId(1L);
user.setRoles(List.of(Role.User));
userPrincipal = UserPrincipal.create(user);
}
#Test
void usedMockUpsShouldNotBeNull() {
assertAll("All mock instaces of classes should not be null",
() -> assertNotNull(fileService),
() -> assertNotNull(userPrincipal),
() -> assertNotNull(user)
);
}
#Test
void collectionOfFilesShouldChangeAfterNewFileIsAdded_Mockito() throws ExecutionException, InterruptedException {
Image image = createImageFile();
List<? super File> files = createFilesCollection();
doReturn(files).when(fileService).getAll(userPrincipal);
int initialSize = fileService.getAll(userPrincipal).size();
fileService.save(image);
doReturn(files.add(image)).when(fileService).save(image);
int newSize = fileService.getAll(userPrincipal).size();
assertNotEquals(newSize, initialSize);
Mockito.verify(fileService, atLeast(2)).getAll(userPrincipal);
Mockito.verify(fileService).save(image);
}
}
I am sorry that you are in the wrong path.
If FileService is just an interface, you do not need to test it.
If FileService is an implementation, you should create an actual instance to test it but not a mock.
The mock is only useful for the direct dependencies of the class that you are testing (i.e FileSerivice) such that you can easily stub the result when calling methods on these dependencies and verify if the class you are testing interacts with them correctly. (i.e. call the correct methods with the correct parameters etc.)
As I cannot see FileSerivice 's source codes , but if UserPrincipal and User are not its dependencies , it does not make sense to mock them.
Also as mentioned by other in the comment, as you are not doing the integration testing with some Spring framework stuff, you should simply rewrite your test as a plain Mockito test which is simpler and run faster :
#ExtendWith(MockitoExtension.class)
public class FileServiceImplTest {
}

How do I test my service which is calling a 3 party rest endpoint?

I'm trying to test my service which is sending a rest request to a 3rd party rest endpoint which is mocked during the integration testing (Assume that I know the required Request Body). The code provided is only a rough outline of what I'm trying to achieve. The rest endpoint itself may not be available at all times, which is why I want to mock it. Let's look at the pseudo-code:
First my test case:
public class FooExampleTest {
private ExampleService exampleService;
#BeforeEach
void setup() {
exampleService = new ExampleService();
}
#Test
void example_TestCase() {
SomeEntity entity = new SomeEntity();
entity.setFoo("Foo!");
// set more fields....
// Calling the service containing the rest request to the 3rd party system
exampleService.sendToClient(entity);
}
}
The service containing the request to the 3rd party system is mocked
#Service
public class SomeServiceImpl {
// some fields
// ...
#Autowired
private RestTemplate restTemplate;
// ......
public void sendToClient(Entity entity){
var uri = "http://localhost:8080/foo"
var clientSystem = new ClientSystemRequest();
clientSystem.setImportField(entity)
log.info("Sending reqeust to client system");
restTemplate.postForObject(uri, clientSystem, ClientSystemResponse.class);
}
}
The 3rd party rest endpoint should be mocked during integration testing
#RestController
#Slf4j
/**
* Mocks the 3rd party rest endpoint
*/
public class 3rdPartyClientsystemResourceTest {
#PostMapping("/foo")
public ResponseEntity<?> foo(#RequestBody #Valid ClientSystemRequest clientSystemRequest){
log.info("Received, with data: {}", clientSystemRequest);
// Do some sort of validation....
return new ResponseEntity<>(HttpStatus.OK);
}
}
Have you looked into using mocking frameworks such as Mockito for example? You could then simply mock the restTemplate and only test the logic of your service rather than it's communication with API.
If you want an integration test than you must make the real call to the API. Otherwise it isn't an integration test.

Spring Boot Integration Testing - Mocking #Service before Application Context Starts?

I have to create a integration test for a microservice X which downloads, processes and importing csv files from external sftp servers. The whole process is started by a spring boot scheduler task which starts a spring batch job for processing and importing the data. The import process is done by the spring batch writer, which is a restTemplate Repository (so it calls post requests to another microservice Y).
I already managed to mock the sftp server, putting a test file on it and the current integration test is downloading the file. (https://github.com/stefanbirkner/fake-sftp-server-rule/)
My problem is, that the task will be scheduled immediately when the application context starts so there is no trigger like a api call. To get the whole integration test working i have to mock the part where the external microservice Y is called through a restTemplate call. This repository is called in the spring batch writer and this repository is created by a repositoryFactory which is a #Service. The repositoryFactory is injected in the spring batch configuration class.
I already tried to use the #MockBean annotation in the test class as well as in a separate test configuration where i am mocking the create() function of the factory to deliver a repository mock. But at some point it does not work and it delivers still the original object which leads to interupt the import job.
I also tried to use the WireMock library, but also in this case it does not catched any api calls and at some point leads to interrupt the sftp socket. (?)
I hope someone could help me out.
The current test:
#NoArgsConstructor
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ContextConfiguration(classes = {JsonHalConfig.class})
#TestPropertySource(locations = "classpath:application-test.properties")
#TestMethodOrder(MethodOrderer.MethodName.class)
public class ImportIT {
#ClassRule
public static final FakeSftpServerRule sftpServer = new FakeSftpServerRule();
private static final String PASSWORD = "password";
private static final String USER = "username";
private static final int PORT = 14022;
#BeforeClass
public static void beforeClass() throws IOException {
URL resource = getTestResource();
if (resource != null) {
sftpServer.setPort(PORT).addUser(USER, PASSWORD);
sftpServer.createDirectories("/home/username/it-space/IMPORT", "/home/username/it-space/EXPORT");
sftpServer.putFile("/home/username/it-space/IMPORT/INBOX/testFile.csv",
resource.openStream());
} else {
throw new IOException("Failed to get test resources");
}
}
private static URL getTestResource() {
return ImportIT.class.getClassLoader().getResource("testFile.csv");
}
#Test
public void test_A_() throws IOException, RepositoryException {
assertTrue(true);
}
}
I tried following configuration classes
(included in #ContextConfiguration)
#Configuration/#TestConfiguration
public class RepositoryTestConfig {
#Bean
#Primary
public IRepositoryFactory repositoryFactory() {
IRepositoryFactory repositoryFactory = mock(IRepositoryFactory.class);
IRepository repository = mock(IRepository.class);
when(repositoryFactory.create(anyString())).thenReturn(repository);
return repositoryFactory;
}
}
(as static class in the test class)
#TestConfiguration/#Configuration
public static class RepositoryTestConfig {
#MockBean
private IRepositoryFactory repositoryFactory;
#PostConstruct
public void initMock(){
IRepository repository = mock(IRepository.class);
Mockito.when(repositoryFactory.create(anyString())).thenReturn(
repository
);
}
}
UPDATE 27.08.2021
I have a RestConfig #Component where a new RestTemplateBuilder is created. I tried to #MockBean this component to deliver a RestTemplateBuilder Mock and injected a MockRestServiceServer object to catch outgoing api calls. But unfortunately it does not work as aspected. Am i missing something? I also tried to create a "TestRestController" to trigger the scheduling of the task but it never delivers the mock...
I normally use #MockBean directly inside my test classes and inject the dedicated (but mocked) repository directly there and not create it inside the test configuration. I also add the test configuration class by #ContextConfiguration so it is loaded in current test context.
Inside my tests I am just using mockito the standard way and prepare the mocked parts as wanted for the dedicated test method.
Here an example snippet:
// ... do some imports ...
#RunWith(SpringRunner.class)
#ContextConfiguration(classes= {XYZSomeWantedClazz.class, DemoXYZMockTest.SimpleTestConfiguration.class})
#ActiveProfiles({Profiles.TEST})
public class DemoXYZMockTest {
//...
#MockBean
private DemoRepository mockedDemoRepository;
// ...
#Test
public void testMethodName() throws Exception{
/* prepare */
List<WantedEntityClazz> list = new ArrayList<>();
// add your wanted data to your list
// apply to mockito:
when(mockedDemoRepository.findAll()).thenReturn(list);
/* execute */
// ... execute the part you want to test...
/* test */
// ... test the results after execution ...
}
#TestConfiguration
#Profile(Profiles.TEST)
#EnableAutoConfiguration
public static class SimpleTestConfiguration{
// .. do stuff if necessary or just keep it empty
}
}
For a complete (old Junit4) working test example please take a look at:
https://github.com/mercedes-benz/sechub/blob/3f176a8f4c00b7e8577c9e3bea847ecfc91974c3/sechub-administration/src/test/java/com/daimler/sechub/domain/administration/signup/SignupAdministrationRestControllerMockTest.java

Mockito calls stubbed method even if using doReturn

I want to create an integration test where a put method is called on a controller and it updates a certain object. During this process, a service class is involved which calls a third party API to do some stuff. In my case, I want to stub the service method which is involved in calling the third party as it is not the point to test the third party.
Having that said, I will present my code and I wait for an answer about why this does not work as expected and/or any other workaround.
This is my service class in which is the method call I want to stub.
public class ProjectService implements SomeInterfance {
// the third party service
private final DamConnector damConnector;
// some other fields
public ProjectDTO save(ProjectDTO projectDTO) {
log.debug("Request to save Project : {}", projectDTO);
// some operations
synchronizeWithDamAndSave(project, parentChanging); //this is the method call I want to be skiped
//other operations
return projectMapper.toDto(project, this);
}
//the method that I want to stub
public Asset synchronizeWithDamAndSave(Project project, boolean includeDocuments) {
Asset asset = synchronizeWithDam(project, includeDocuments);
projectRepository.save(project);
return asset;
}
}
And my integration test class:
#SpringBootTest(classes = SppApp.class)
public class ProjectResourceIT {
//other fields
//my service use autowire as it needs to make the service calls
#Autowired
private ProjectService projectService;
//this is my setup method where I create the spy of project service and define the doReturn behavior when my method is called
#BeforeEach
public void setup() {
ProjectService spyProjectService = Mockito.spy(projectService);
Mockito.doReturn(new Asset()).when(spyProjectService).synchronizeWithDamAndSave(Mockito.any(Project.class),Mockito.anyBoolean());
MockitoAnnotations.initMocks(this);
final ProjectResource projectResource = new ProjectResource(spyProjectService, clientService, securityService);
this.restProjectMockMvc = MockMvcBuilders.standaloneSetup(projectResource)
.setCustomArgumentResolvers(pageableArgumentResolver)
.setControllerAdvice(exceptionTranslator)
.setConversionService(createFormattingConversionService())
.setMessageConverters(jacksonMessageConverter)
.setValidator(validator).build();
}
}
...
public void updateProject() throws Exception {
// initialization of the test
// this is where I call my controller
restProjectMockMvc.perform(put("/api/projects")
.contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(projectDTO)))
.andExpect(status().isOk());
}
}
The problem in my case is that mockito enters in synchronizeWithDamAndSave method just after
Mockito.doReturn(new Asset()).when(spyProjectService).synchronizeWithDamAndSave(Mockito.any(Project.class),Mockito.anyBoolean());
this line is called, before the method to be called from the rest api.
What should I do? Any hints about why this is happening?
Spring Boot's proxies are not working wit Mockito. Use #SpyBean instead of #Autowired.

How can I properly mock a Principal object in Spring?

First of all, I have the following endpoint method present within a class called RecipeController:
#RequestMapping(value = {"/", "/recipes"})
public String listRecipes(Model model, Principal principal){
List<Recipe> recipes;
User user = (User)((UsernamePasswordAuthenticationToken)principal).getPrincipal();
User actualUser = userService.findByUsername(user.getUsername());
if(!model.containsAttribute("recipes")){
recipes = recipeService.findAll();
model.addAttribute("nullAndNonNullUserFavoriteRecipeList",
UtilityMethods.nullAndNonNullUserFavoriteRecipeList(recipes, actualUser.getFavoritedRecipes()));
model.addAttribute("recipes", recipes);
}
if(!model.containsAttribute("recipe")){
model.addAttribute("recipe", new Recipe());
}
model.addAttribute("categories", Category.values());
model.addAttribute("username", user.getUsername());
return "recipe/index";
}
As you can see above, the method takes as a second parameter a Principal object. When running the application, the parameter points to a non-null object as expected. It contains information about the user that is currently logged in within the application.
I have created a test class for the RecipeController called RecipeControllerTest. This class contains a single method called testListRecipes.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
#WebAppConfiguration
public class RecipeControllerTest{
#Mock
private RecipeService recipeService;
#Mock
private IngredientService ingredientService;
#Mock
private StepService stepService;
#Mock
private UserService userService;
#Mock
private UsernamePasswordAuthenticationToken principal;
private RecipeController recipeController;
private MockMvc mockMvc;
#Before
public void setUp(){
MockitoAnnotations.initMocks(this);
recipeController = new RecipeController(recipeService,
ingredientService, stepService, userService);
mockMvc = MockMvcBuilders.standaloneSetup(recipeController).build();
}
#Test
public void testListRecipes() throws Exception {
User user = new User();
List<Recipe> recipes = new ArrayList<>();
Recipe recipe = new Recipe();
recipes.add(recipe);
when(principal.getPrincipal()).thenReturn(user);
when(userService.findByUsername(anyString()))
.thenReturn(user);
when(recipeService.findAll()).thenReturn(recipes);
mockMvc.perform(get("/recipes"))
.andExpect(status().isOk())
.andExpect(view().name("recipe/index"))
.andExpect(model().attributeExists("recipes"))
.andExpect(model().attributeExists("recipe"))
.andExpect(model().attributeExists("categories"))
.andExpect(model().attributeExists("username"));
verify(userService, times(1)).findByUsername(anyString());
verify(recipeService, times(1)).findAll();
}
}
As you can see in this second snippet, I tried to mock the Principal object within the test class, using the UsernamePasswordAuthenticationToken implementation.
When I run the test, I get a NullPointerException, and the stacktrace points me to the following line from the first snippet of code:
User user = (User)((UsernamePasswordAuthenticationToken)principal).getPrincipal();
The principal object passed as a parameter to the listRecipes method from is still null, even though I tried to provide a mock object.
Any suggestions ?
Create a class that implements Principal:
class PrincipalImpl implements Principal {
#Override
public String getName() {
return "XXXXXXX";
}
}
Sample test:
#Test
public void login() throws Exception {
Principal principal = new PrincipalImpl();
mockMvc.perform(get("/login").principal(principal)).andExpect(.........;
}
Spring MVC is very flexible with controller arguments, which lets you put most of the responsibility of looking up information onto the framework and focus on writing the business code. In this particular case, while you can use Principal as a method parameter, it's usually much better to use your actual principal class:
public String listRecipes(Model model, #AuthenticationPrincipal User user)
To actually set the user for a test, you need to work with Spring Security, which means adding .apply(springSecurity()) to your setup. (Complications like this, by the way, are the main reason I dislike using standaloneSetup, as it requires you to remember to duplicate your exact production setup. I recommend writing actual unit tests and/or full-stack tests.) Then annotate your test with #WithUserDetails and specify the username of the test user.
Finally, as a side note this controller pattern can be simplified significantly with Querydsl, as Spring is able to inject a Predicate that combines all of the filter attributes you're looking up by hand, and then you can pass that predicate to a Spring Data repository.
Did you try using...?
#Test
#WithMockUser(username = "my_principal")
public void testListRecipes() {
...

Categories

Resources