I have the following controller which I want to test:
#RestController
#RequestMapping("/api/account")
public class AccountController {
private final AccountService accountService;
private UUID correspondentId;
public AccountController(AccountService accountService, #Value("${app.correspondent}") String correspondentId) {
this.accountService = accountService;
this.correspondentId = UUID.fromString(correspondentId);
}
#GetMapping("")
#PreAuthorize("hasRole('CUSTOMER')")
Mono<String> index(Authentication auth) {
return.....;
}
I tried to mock a test this way:
#Test
public void testMockUser() {
AccountService accountService = Mockito.mock(AccountService.class);
Mockito.mock(AccountController.class, withSettings().useConstructor(accountService,
UUID.randomUUID()));
webTestClient
.mutateWith(jwtMutator())
.get().uri("/api/account/")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk();
}
But I get error:
Caused by: java.lang.IllegalArgumentException: Invalid UUID string:
Do you know what is the proper way to mock the constructor value?
I think the problem might be related to creating the bean itself and not the mock (probably it resolves #Value("${app.correspondent}") to "" in the test environment and then fails as it's not an UUID. I suggest putting something like app.correspondent=d7aa282f-a8aa-40f9-bce6-08ce6737b6e1 in the properties and check it out.
Also, it looks like there's another problem you might face: it's AccountService, String method signature in the controller and AccountService, UUID in the mock. Pretty sure this will give another error with mock construction. Though, this is easy to fix with a simple toString.
As soon as you use Spring then in application-test.properties you could add
app.correspondent=some-value
And in test class you use #MockBean as:
class TestClass {
#MockBean
private AccountService accountService;
}
At context start-up Spring will create AccountController with the value injected from application-test.properties and mocked accountService.
Related
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.
I am creating a spring boot API for my application. I am attempting to unit test my service implementation using mockito to mock out the detail. The service will add a new building entity to the database. Below is the Service implementation and the test implementation.
Building Service:
#Slf4j
#Service
public class BuildingServiceImpl implements BuildingService {
private BuildingRepository buildingRepository;
private BuildingRequestToEntityMapper buildingRequestToEntityMapper;
public BuildingServiceImpl(BuildingRepository buildingRepository, BuildingRequestToEntityMapper
buildingRequestToEntityMapper){
this.buildingRepository=buildingRepository;
this.buildingRequestToEntityMapper=buildingRequestToEntityMapper;
}
public HttpStatus addBuilding(BuildingRequest buildingRequest){
log.info("Inside addBuilding() service");
BuildingEntity buildingEntity = buildingRequestToEntityMapper.map(buildingRequest);
buildingRepository.save(buildingEntity);
log.info("Building saved "+ buildingEntity);
return HttpStatus.CREATED;
}
BuildingServiceImpl_UT
#RunWith(MockitoJUnitRunner.class)
public class BuildingServiceImpl_UT {
#Mock
BuildingRequestToEntityMapper buildingRequestToEntityMapper;
#Mock
BuildingRepository buildingRepository;
#InjectMocks
BuildingServiceImpl buildingServiceImpl;
#Test
public void buildingService_MapsRequest_AndSaveEntity(){
BuildingRequest buildingRequest = BuildingRequest.builder()
.name("TestName")
.telephone("4444444444")
.postcode("TEst")
.address("testAddress").build();
when(buildingServiceImpl.addBuilding(any(BuildingRequest.class))).thenReturn(HttpStatus.CREATED);
when(buildingRepository.save(any(BuildingEntity.class))).thenReturn(new BuildingEntity());
buildingServiceImpl.addBuilding(buildingRequest);
verify(buildingRepository, times(1)).save(any());
}
I have mocked the mapper and repository and injected them into the service, but when i run the test I get a null pointer exception at the first when().thenReturn() statement in the test class. Any help please. Thanks
I don't understand your first when().thenReturn()! You try to do this on the buildingServiceImpl wich is not a mock! Further more this makes no sense because you want to test this methode!
I think you should define a when().thenReturn() for the mock buildingRequestToEntityMapper, but in your implementation you don't need to define a return for buildingRequestToEntityMapper.map(). In this case the variable buildingEntity will have the value null which should work in your test case.
#RunWith(MockitoJUnitRunner.class)
public class BuildingServiceImpl_UT {
#Mock
BuildingRequestToEntityMapper buildingRequestToEntityMapper;
#Mock
BuildingRepository buildingRepository;
#InjectMocks
BuildingServiceImpl buildingServiceImpl;
#Test
public void buildingService_MapsRequest_AndSaveEntity(){
BuildingRequest buildingRequest = BuildingRequest.builder()
.name("TestName")
.telephone("4444444444")
.postcode("TEst")
.address("testAddress").build();
when(buildingRepository.save(any(BuildingEntity.class))).thenReturn(new BuildingEntity());
buildingServiceImpl.addBuilding(buildingRequest);
verify(buildingRepository, times(1)).save(any());
verify(buildingRequestToEntityMapper).map(any());
}
This:
when(buildingServiceImpl.addBuilding(any(BuildingRequest.class))).thenReturn(HttpStatus.CREATED);
is not necessary, you want to test method: addBuilding not mock it.
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
I have a Spring MVC REST controller class that has a private instance boolean field injected via #Value ,
#Value("${...property_name..}")
private boolean isFileIndex;
Now to unit test this controller class, I need to inject this boolean.
How do I do that with MockMvc?
I can use reflection but MockMvc instance doesn't give me underlying controller instance to pass to Field.setBoolean() method.
Test class runs without mocking or injecting this dependency with value always being false. I need to set it to true to cover all paths.
Set up looks like below.
#RunWith(SpringRunner.class)
#WebMvcTest(value=Controller.class,secure=false)
public class IndexControllerTest {
#Autowired
private MockMvc mockMvc;
....
}
You can use #TestPropertySource
#TestPropertySource(properties = {
"...property_name..=testValue",
})
#RunWith(SpringRunner.class)
#WebMvcTest(value=Controller.class,secure=false)
public class IndexControllerTest {
#Autowired
private MockMvc mockMvc;
}
You can also load your test properties form a file
#TestPropertySource(locations = "classpath:test.properties")
EDIT: Some other possible alternative
#RunWith(SpringRunner.class)
#WebMvcTest(value=Controller.class,secure=false)
public class IndexControllerTest {
#Autowired
private MockMvc mockMvc;
#Autowired
private Controller controllerUnderTheTest;
#Test
public void test(){
ReflectionTestUtils.setField(controllerUnderTheTest, "isFileIndex", Boolean.TRUE);
//..
}
}
My preferred option would be to set it in the constructor and annotate the constructor parameter with the #Value. You could then pass in whatever you want in the test.
See this answer
I am having trouble using the #MockBean annotation. The docs say MockBean can replace a bean within the context, but I am getting a NoUniqueBeanDefinitionException within my unit test. I can't see how to use the annotation. If I can mock the repo, then obviously there will be more than one bean definition.
I am following the examples found here: https://spring.io/blog/2016/04/15/testing-improvements-in-spring-boot-1-4
I have a mongo repository:
public interface MyMongoRepository extends MongoRepository<MyDTO, String>
{
MyDTO findById(String id);
}
And a Jersey resource:
#Component
#Path("/createMatch")
public class Create
{
#Context
UriInfo uriInfo;
#Autowired
private MyMongoRepository repository;
#POST
#Produces(MediaType.APPLICATION_JSON)
public Response createMatch(#Context HttpServletResponse response)
{
MyDTO match = new MyDTO();
match = repository.save(match);
URI matchUri = uriInfo.getBaseUriBuilder().path(String.format("/%s/details", match.getId())).build();
return Response.created(matchUri)
.entity(new MyResponseEntity(Response.Status.CREATED, match, "Match created: " + matchUri))
.build();
}
}
And a JUnit test:
#RunWith(SpringRunner.class)
#SpringBootTest
public class TestMocks {
#Autowired
private TestRestTemplate restTemplate;
#MockBean
private MyMongoRepository mockRepo;
#Before
public void setup()
{
MockitoAnnotations.initMocks(this);
given(this.mockRepo.findById("1234")).willReturn(
new MyDTO());
}
#Test
public void test()
{
this.restTemplate.getForEntity("/1234/details", MyResponseEntity.class);
}
}
Error message:
Field repository in path.to.my.resources.Create required a single bean, but 2 were found:
- myMongoRepository: defined in null
- path.to.my.MyMongoRepository#0: defined by method 'createMock' in null
It's a bug: https://github.com/spring-projects/spring-boot/issues/6541
The fix is in spring-data 1.0.2-SNAPSHOT and 2.0.3-SNAPSHOT : https://github.com/arangodb/spring-data/issues/14#issuecomment-374141173
If you aren't using these version, you can work around it by declaring the mock with its name:
#MockBean(name="myMongoRepository")
private MyMongoRepository repository;
In response to your comment
From Spring's doc:
For convenience, tests that need to make REST calls to the started
server can additionally #Autowire a TestRestTemplate which will
resolve relative links to the running server.
Reading this, I think you need to declare #SpringBootTest with a web environment:
#SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
If your spring boot doesn't start the web environment, then what is the need for TestRestTemplate. Thus, I guess spring does not even make it available.
I had the same "issue" in spring-boot 2.3.9 but it's not a bug, it's problem with the configuration of beans.
At least, There are two ways to solve it:
Set name parameter in #MockBean annotation:
In the test, add a name to MockBean:
#MockBean(name="myRepository")
private MyRepository diffrentName;
and in the production codebase use myRepository as filed name :
#Autowired
private MyRepository myRepository;
The name of #MockBean must be the same as the name of the field.
Name a MockBean filed the same as a dependency in code.
In the test, use the correct name of MockBean filed:
#MockBean
private MyRepository customRepository;
and in the production codebase use customRepository as filed name :
#Autowired
private MyRepository customRepository;
in that way, You indicate which bean You want to use.
I hope this will be helpful for someone.
Just add below in POM.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>