Mockito Controller Test I'm getting error - java

all I'm trying to test my controller. But I'm getting error.
Wanted but not invoked: accountService.findAccount("9090");
-> at com.ontavio.bank.ControllerTests.givenId_Cash_thenReturnJson(ControllerTests.java:67)
However, there was exactly 1 interaction with this mock:
accountService.cash(
"9090",
com.ontavio.bank.model.CashTransaction#2f408960 );
#PostMapping("/cash/{accountNumber}")
public ResponseEntity<TransactionStatus> cash(#PathVariable String accountNumber, #RequestBody CashTransaction cashTransactionRequest) {
return ResponseEntity.ok(accountService.cash(accountNumber, cashTransactionRequest));
}
---
public TransactionStatus cash(String accountNumber, CashTransaction cashTransaction) {
Account account = this.findAccount(accountNumber);
cashTransaction.setType(TransactionTypes.CASH_TRANSACTION.getRelation());
account.post(cashTransaction);
accountRepository.save(accountMapper.AccountToAccountEntity(account));
return TransactionStatus.createTransactionStatus(HttpStatus.OK, "");
}
----
#SpringBootTest
#ContextConfiguration
#AutoConfigureMockMvc
class ControllerTests {
#Spy
#InjectMocks
private AccountController accountController;
#Mock
private AccountService accountService;
#Mock
private AccountMapper accountMapper;
#Mock
private AccountRepository accountRepository;
#Test
public void givenId_Cash_thenReturnJson()
throws Exception {
Account account = new Account("James Harden", "9090");
CashTransaction cashTransaction = new CashTransaction(100.0);
AccountEntity accountEntity = new AccountEntity();
accountEntity.setOwner(account.getOwner());
accountEntity.setAccountNumber(account.getAccountNumber());
TransactionStatus transactionStatus = new TransactionStatus();
transactionStatus.setStatus(HttpStatus.OK.name());
doReturn(account).when(accountService).findAccount( "9090");
doReturn(transactionStatus).when(accountService).cash("9090", depositTransaction);
doReturn(accountEntity).when(accountRepository).save(accountEntity);
ResponseEntity<TransactionStatus> result = accountController.cash( "9090", cashTransaction);
verify(accountService, times(1)).findAccount("9090");
assertEquals("OK", result.getBody().getStatus());
}

My guess is that you are missing
....
#ExtendWith(SpringExtension.class)
#SpringBootTest
#ContextConfiguration
#AutoConfigureMockMvc
class ControllerTests {
.....
above your ControllerTest class. Also i would suggest to use #Autowired instead of #Spy and #InjectMocks, and Annotate your mocked Service,Mapper and repository with #MockBean
OR (depending on your use case)
Use the annotation
#ExtendWith(MockitoExtension.class)
then you have a Mockito test which does not start the spring context. You could remove #SpringBootTest #ContextConfiguration #AutoConfigureMockMvc then. You donĀ“t need #AutoConfigureMockMvc anyways since you do not execute a http call, but call the RestControllers method directly. I dont see why you would need the spring context in your test, so I would prefer to use the Mockito Test. It is much faster since the applicationContext does not start.
Mixing Spring and Mockito testing environment works, but can make for weird circumstances. I generally try to stay in one. Either test a class and mock everything with Mockito or write an integration Test and use the Spring utilities.

Related

Spring Boot unit test constructor injection

I'm using Spring Boot to create a REST API and write some unit tests on my controllers.
I know that the recommended manner to inject beans in spring is the constructor injection.
But when i add the #SpringBootTest annotation to my test class, I can not inject my controller class with constructor, I find myself obliged to use #Autowired.
Have some explanation and is there another way to use constructor injection with SpringBootTest.
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class PersonControllerTest {
#LocalServerPort
private int port;
#Autowired
private PersonController controller;
#Autowired
private TestRestTemplate restTemplate;
#Test
public void greetingShouldReturnDefaultMessage() throws Exception {
assertThat(this.restTemplate.getForObject("http://localhost:" + port + "/cvtech/Persons/",
String.class)).contains("content");
}
#Test
public void contextLoads() throws Exception {
assertThat(controller).isNotNull();
}
#Test
void findAllByJob() {
}
}
For those using Kotlin, using field-injection means having to use lateinit var fields. Which is far from ideal.
It is possible to use constructor injection on SpringBoot tests however, using the #TestConstructor:
#ExtendWith(SpringExtension::class)
#TestConstructor(autowireMode = ALL)
#SpringBootTest(
classes = [MyApplication::class],
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
)
internal class MyIntegrationTest(
val beanA: BeanA,
#Qualifier("some qualified") val beanB: BeanB,
) {
...
// Tests can use the beans injected in the constructor without any problems
...
}
It's fine for your test to use field injection as the Test itself is not part of your domain; the test won't be part of your application context.
Also
You don't want to use SpringBootTest to test a controller, because that will wire ALL beans which can be way too heavy and time-consuming. Instead, you probably only want to create your controller and it's dependencies.
So your best option is to use #WebMvcTest which will only create the beans required for testing the specified controller.
#ExtendWith(SpringExtension.class)
#WebMvcTest(controllers = PersonController.class)
class PersonControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
public void greetingShouldReturnDefaultMessage() throws Exception {
mockMvc.perform(get("/cvtech/Persons"))
.andExpect(status().isOk())
.andExpect(content().string(contains("content")));
}
}
Note that #WebMvcTest will search for a class annotated with #SpringBootConfiguration as it's default configuration. If it does not find it, or you want to manually specify some configuration classes, also annotate the test with #ContextConfiguration.
Also, as a sidenote, when using TestRestTemplate, you don't need to specify host and port. Just call restTemplate.getForObject("/cvtech/persons", String.class));
Same when using MockMvc or WebTestClient.

Is there a simple reason for why Mock and SpyBean can't be used together?

Initially, I had annotated a repository with #SpyBean, to track the number of times its methods were called. Then I realized I needed to annotate it with #Mock in order to mock what was returned from the repository.
So I ended up with:
...
#SpyBean
#Mock
SomeService someservice;
...
which caused all my tests using that service to fail with
java.lang.IllegalStateException: Failed to load ApplicationContext
So I removed #SpyBean and then everything worked again, except the reason I wanted to use #SpyBean was so that I could mock some methods and not others. Does that not work with #Mock?
Edit: someone asked for the file
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {RestApplication.class }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ActiveProfiles({ "test" })
public class SomeServiceTest {
#Autowired
SomeService someService;
//#SpyBean
#Mock
SomeRepository someRepository;
String FILENAME = "txt";
#Before
public void setUp() {
someService = new SomeService(someRepository);
}
#Test
public void testFileName() {
when(someRepository.findSomethingByAttribute(anyString()))
.thenReturn(new SomePOJO("", FILENAME, ""));
someService.runFileName(FILENAME);
verify(someRepository, times(1)).save(any(SomePOJO.class));
}

Can't mock repository when testing with mockmvc

I have this quite simple controller class and a simple (jpa) repository.
What I want to do is to test it's api but mock it's repository and let it return an object or not depending on the test case.
My problem now is that I don't know how to do that.
I know how to mock a repository and inject it to a controller/service class with #Mock / #InjectMocks / when().return()
But I fail when I want to do the same after doing a request with MockMvc.
Any help is highly appreciated
The controller
import java.util.Optional;
#RestController
#Slf4j
public class ReceiptController implements ReceiptsApi {
#Autowired
private ReceiptRepository receiptRepository;
#Autowired
private ErrorResponseExceptionFactory exceptionFactory;
#Autowired
private ApiErrorResponseFactory errorResponseFactory;
#Override
public Receipt getReceipt(Long id) {
Optional<ReceiptEntity> result = receiptRepository.findById(id);
if (result.isEmpty()) {
throw invalid("id");
}
ReceiptEntity receipt = result.get();
return Receipt.builder().id(receipt.getId()).purchaseId(receipt.getPurchaseId()).payload(receipt.getHtml()).build();
}
private ErrorResponseException invalid(String paramName) {
return exceptionFactory.errorResponse(
errorResponseFactory.create(HttpStatus.NOT_FOUND.value(), "NOT_VALID", String.format("receipt with id %s not found.", paramName))
);
}
}
And it's test class
#WebMvcTest(ReceiptController.class)
#RestClientTest
public class ReceiptControllerTest {
#InjectMocks
private ReceiptController receiptController;
#Mock
private ReceiptRepository receiptRepository;
#Mock
private ErrorResponseExceptionFactory exceptionFactory;
#Mock
private ApiErrorResponseFactory errorResponseFactory;
private MockMvc mvc;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mvc = MockMvcBuilders.standaloneSetup(
new ReceiptController())
.build();
}
#Test
public void getReceiptNotFoundByRequest() throws Exception {
mvc.perform(MockMvcRequestBuilders
.get("/receipt/1")
.header("host", "localhost:1348")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound());
}
//TODO: Finish this test
#Test
public void getReceiptFoundByRequest() throws Exception {
ReceiptEntity receipt1 = ReceiptEntity.builder().id(99999L).purchaseId(432L).html("<html>").build();
when(receiptRepository.findById(1L)).thenReturn(Optional.of(ReceiptEntity.builder().id(1L).purchaseId(42L).html("<html></html>").build()));
ResultActions result = mvc.perform(get("/receipt/1")
.header("host", "localhost:1348")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
Within your setUp() method, you're using Mockito to mock the beans annotated with #Mock and inject them in a new instance of ReceiptController, which is then put into the field annotated with #InjectMocks.
On the next line, you're setting up MockMvc with a new instance of ReceiptController (you use new ReceiptController()), rather than using the instance that Mockito created. That means that all fields within that instance will be null.
This pretty much boils down exactly to Why is my Spring #Autowired field null.
To solve this, you could pass receiptController to MockMvc. In that case, you could also remove the #WebMvcTest and #RestClientTest as you're not using them.
Alternatively, you could setup your test with #RunWith(SpringRunner.class), and properly set up a Spring test by using #Autowired in stead of #InjectMocks and #MockBean in stead of #Mock. In that case, you don't need a setUp() method at all, as MockMvc could be autowired by Spring.
#WebMvcTest and MockMvc allows you to do integration testing, not unit testing.
They allow you to boot an actual Spring application (using a random port) and test the actual controller class with its dependencies. This means that the variables you declared at the top of your test are not actually used in your current setup.
If you wish to unit-test your controller, you can remove #WebMvcTest, create mock dependencies (like you did) and call your methods directly instead of using MockMvc.
If you really wish to write an integration test, you need to mock your external dependencies (the database for example). You can configure spring to use an embedded database (H2 for example) in the test environment, so that you do not affect your real database.
See an example here : https://www.baeldung.com/spring-testing-separate-data-source

Spring Boot unit test with SpringSecurity and Mock

Currently I have several unit tests that's working correctly.
In my unit test, on init, I have included the following code:
#Mock
private UsersServices usersServices;
#InjectMocks
private UsersController usersControllers;
#Before
public void init() {
this.mvc = MockMvcBuilders.standaloneSetup(usuariosController)
.addFilter(springSecurityFilterChain)
.setControllerAdvice(new UsuariosControllerAdvice(logService)).build();
}
this worked great, but some authorizations annotations, like #PreAuthorize are ignored. (In my WebSecurityConfig, I already added the #EnableGlobalMethodSecurity(prePostEnabled = true) annotation.
So, after some time, I found the following code:
#Mock
private UsersServices usersServices;
#InjectMocks
private UsersController usersControllers;
#Autowired
private WebApplicationContext wac;
#Before
public void init() {
this.mvc = MockMvcBuilders
.webAppContextSetup(wac)
.addFilter(springSecurityFilterChain)
apply(SecurityMockMvcConfigurers.springSecurity(springSecurityFilterChain))
.build();
}
and now, the authorization annotations (#PreAuthorize) works, but the UsersServices mock don't. When I call my controller method on unit tests, the real UserServices was called, not the mock.
Here is a unit test that mock a UserServices:
when(usersServices.getUserAvatar(anyString())).thenReturn(
CompletableFuture.completedFuture(Optional.empty()));
MvcResult result = mvc.perform(
get("/api/users/avatar/{login:.+}", "mock.user")
.header("Authorization", testHelpers.buildJwtToken("USER")))
.andReturn();
mvc.perform(asyncDispatch(result))
.andExpect(status().isNotFound());
without the standaloneSetup, the real userServices.getUserAvatar is called.
This happens because your WebApplicationContext is not aware of your UsersController with mocked UsersServices. To fix this you have two options:
The first option is to use
#MockBean
private UsersServices usersServices;
instead of:
#Mock
private UsersServices usersServices;
This will add mocked bean into application context so that the Spring is aware of it and, thus, will use it instead of a real one.
The second option is to set your controller directly inside WebApplicationContext manually. This option should not be "tried at home", but can be a workaround for cases when you do not have #MockedBean because of the old spring version:
AutowireCapableBeanFactory factory = wac.getAutowireCapableBeanFactory();
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) factory;
registry.removeBeanDefinition(beanId);
//create newBeanObj through GenericBeanDefinition
registry.registerBeanDefinition(beanId, newBeanObj);

Injecting mocks with Mockito does not work

I'm using Mockito to test my Spring project, but the #InjectMocks seems not working in injecting a mocked service into another Spring service(bean).
Here is my Spring service that I want to test:
#Service
public class CreateMailboxService {
#Autowired UserInfoService mUserInfoService; // this should be mocked
#Autowired LogicService mLogicService; // this should be autowired by Spring
public void createMailbox() {
// do mething
System.out.println("test 2: " + mUserInfoService.getData());
}
}
And below is the service that I want to mock:
#Service
public class UserInfoService {
public String getData() {
return "original text";
}
}
My test code is here:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/spring/root-context.xml" })
public class CreateMailboxServiceMockTest {
#Mock
UserInfoService mUserInfoService;
#InjectMocks
#Autowired
CreateMailboxService mCreateMailboxService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void deleteWithPermission() {
when(mUserInfoService.getData()).thenReturn("mocked text");
System.out.println("test 1: " + mUserInfoService.getData());
mCreateMailboxService.createMailbox();
}
}
but the result would like
test 1: mocked text
test 2: original text // I want this be "mocked text", too
it seems that the CreateMailboxService didn't get the mocked UserInfoService but using Spring's autowired bean.
Why is my #InjectMocks not working?
In my case, I had a similar issue when I worked with JUnit5
#ExtendWith(MockitoExtension.class)
class MyServiceTest {
...
#InjectMocks
MyService underTest;
#Test
void myMethodTest() {
...
}
underTest was null.
The cause of the problem was that I used #Test from JUnit4 package import org.junit.Test; instead JUnit5 import org.junit.jupiter.api.Test;
For those who stumbles on this thread and are running with JUnit 5 you need to replace
#RunWith(SpringJUnit4ClassRunner.class)
with
#ExtendWith(MockitoExtension.class)
#RunWith(JUnitPlatform.class)
Further reading here. Unfortunately there is no hint when executing the test cases with JUnit 5 using the old annotation.
You can create package level setter for mUserInfoService in CreateMailboxService class.
#Service
public class CreateMailboxService {
#Autowired UserInfoService mUserInfoService; // this should be mocked
#Autowired LogicService mLogicService; // this should be autowired by Spring
public void createMailbox() {
// do mething
System.out.println("test 2: " + mUserInfoService.getData());
}
void setUserInfoService(UserInfoService mUserInfoService) {
this.mUserInfoService = mUserInfoService;
}
}
Then, you can inject that mock in the test using the setter.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/spring/root-context.xml" })
public class CreateMailboxServiceMockTest {
#Mock
UserInfoService mUserInfoService;
CreateMailboxService mCreateMailboxService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mCreateMailboxService = new CreateMailboxService();
mCreateMailboxService.setUserInfoService(mUserInfoService);
}
...
}
This way you can avoid problems with #InjectMocks and Spring annotations.
If you are trying to use the #Mock annotation for a test that relies directly on Spring injection, you may need to replace #Mock with #MockBean #Inject (both annotations), and #InjectMocks with #Inject. Using your example:
#MockBean
#Inject
UserInfoService mUserInfoService;
#Inject
CreateMailboxService mCreateMailboxService;
I had a pretty similar situation. I am writing it down just in case any reader is going through the same. In my case I found that the problem was that I was setting my injected variable as final in the local class.
Following your example, I had things like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/spring/root-context.xml" })
public class CreateMailboxServiceMockTest {
#Mock
UserInfoService mUserInfoService;
#InjectMocks
CreateMailboxService mCreateMailboxService = new CreateMailboxService(mUserInfoService);
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void deleteWithPermission() {
...
}
}
But in this class I had it like this:
#Service
public class CreateMailboxService {
private final UserInfoService mUserInfoService; // it is NOT injecting Mocks just because it is final! (all ok with private)
private final LogicService mLogicService; // it is NOT injecting Mocks just because it is final! (all ok with private)
#Autowired
public CreateMailboxService(UserInfoService mUserInfoService, LogicService mLogicService) {
this.mUserInfoService = mUserInfoService;
this.mLogicService = mLogicService;
}
public void createMailbox() {
...
}
}
Just deleting the final condition, #InjectMocks problem was solved.
For those who are running with JUnit 5 you need to replace the #RunWith(SpringJUnit4ClassRunner.class) with #ExtendWith(MockitoExtension.class).
For further reading take a look here.
there is no need of #Autowired annotation when you inject in the test class. And use the mock for the method to get your mocked response as the way you did for UserInfoService.That will be something like below.
Mockito.when(mCreateMailboxService. getData()).thenReturn("my response");
You can use MockitoJUnitRunner to mock in unit tests.
Use #Mock annotations over classes whose behavior you want to mock.
Use #InjectMocks over the class you are testing.
Its a bad practice to use new and initialize classes (better to go for dependency injection) or to introduce setters for your injections. Using setter injection to set dependencies only for tests is wrong as production code should never be altered for tests.
#RunWith(MockitoJUnitRunner.class)
public class CreateMailboxServiceMockTest {
#Mock
UserInfoService mUserInfoService;
#InjectMocks
CreateMailboxService mCreateMailboxService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
...
}

Categories

Resources