I'm new with Spring and Mockito testing. I couldn't find answer to my problem on stackoverflow.
I have the following classes:
#EnableScheduling
#Service
public class ServiceEx {
private Queue<Object> tasks = new ConcurrentLinkedQueue();
public void addItem(Object task) {
tasks.add(task);
}
#Scheduled(fixedRate = 30000)
public void executePendingTask() {
tasks.remove();
}
public void drop() {
tasks.clear();
}
public boolean isEmpty() {
return tasks.isEmpty();
}
}
#Controller
#RequestMapping("/drop")
public class ControllerEx {
private ServiceEx service;
#Inject
public ControllerEx(ServiceEx service) {
this.service = service;
}
#RequestMapping(method = RequestMethod.GET)
public String dropTasks(Model model) {
service.drop();
return "redirect:/home";
}
}
And my testing class looks like :
public class ControllerTest {
#Inject
private ServiceEx service;
#InjectMocks
private ControllerEx controller;
private MockMvc mockMvc;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
#Test
public void test() {
service.add(task1);
service.add(task2);
this.mockMvc.perform(get("/drop")).andExpect(status().is3xxRedirection());
assertTrue(service.isEmpty());
}
}
My problem is that service is null in both ControlleEx and ControllerTest and i don't want to mock it. I tried several annotations, but didn't find which one to use.
What have i done wrong?
Looking at your code you seem to be mixing unit tests with integration tests.
MockMvc is usually used to do integration tests starting at the controller level. this means that you need a configured and started applicationContext to successfully use that. If that's what this test class is supposed to do then I don't see the use of Mocks, unless you wire them in the application context, your controller won't use them.
If you want to do integration testing, but want to Mock or stub out certain functionality (which in my opinion should only be done because of dependencies on external systems) you should think about wiring some stubs in your applicationContext for this test instead of trying to use Mockito for this.
Also keep in mind that by default, the applicationContext is re-used to run all your tests, which could mean that stubbing for one test could affect an other.
Related
Is it possible to write unit test using Junit 5 mockito for retryable annotations?
I am having a service interface which has only one method, which downloads the file from remote url
#service
interface downloadpdf{
#Retryable(value = { FileNotFoundException.class, HttpClientErrorException.class }, maxAttempts = 5, backoff = #Backoff(delay = 1000))
public string downloadpdffile(string remoteurl, string pdfname);
}
I have tried referring sites and found using Spring4JunitRunner implementation to test retry. Got confused with implementation. Is it possible to write unit test using Junit 5 mockito for retryable annotations?. Could you please elaborate on the solution here?
You need to use #SpringJUnitConfig (which is the equivalent of the JUnit4 runner). Or #SpringBootTest as you are using Boot.
#Retryable only works with beans managed by Spring - it wraps the bean in a proxy.
#SpringBootApplication
#EnableRetry
public class So71849077Application {
public static void main(String[] args) {
SpringApplication.run(So71849077Application.class, args);
}
}
#Component
class RetryableClass {
private SomeService service;
void setService(SomeService service) {
this.service = service;
}
#Retryable
void retryableMethod(String in) {
service.callme();
throw new RuntimeException();
}
#Recover
void recover(Exception ex, String in) {
service.failed();
}
}
interface SomeService {
void callme();
void failed();
}
#SpringBootTest
class So71849077ApplicationTests {
#MockBean
SomeService service;
#Test
void testRetry(#Autowired RetryableClass retryable) {
SomeService service = mock(SomeService.class);
retryable.setService(service);
retryable.retryableMethod("foo");
verify(service, times(3)).callme();
verify(service).failed();
}
}
I was also trying to implement this using Junit5.
Tried various options but that didn't help. Then after googling for few hours, got the following link and it helped to succeed.
https://doctorjw.wordpress.com/2022/04/29/spring-testing-a-single-bean-in-junit-5-springextension/
Reference code below, for detailed explanation, please refer the blog.
#Component
public class MyClass {
private ObjectMapper objectMapper;
private RestTemplate restTemplate;
#Value("${testValue:5}")
private int value;
#Retryable(....)
public void doStuff() throws SomeException {
...
}
}
What I’ve discovered is, if I declare my test class this way:
#ExtendWith( SpringExtension.class )
#Import( { MyClass.class, ObjectMapper.class } )
#EnableRetry
public class MyClassTest {
#Autowired
private MyClass myClass;
#MockBean
private RestTemplate restTemplate;
#Autowired
private ObjectMapper objectMapper;
#BeforeEach
public void setup() {
// If we are going to jack with the object configuration,
// we need to do so on the actual object, not the Spring proxy.
// So, use AopTestUtils to get around the proxy to the actual obj.
TestingUtils.setFieldValue( AopTestUtils.getTargetObject( myClass ), "value", 10 );
}
}
You will notice the inclusion of 1 other class, TestingUtils.class. This class looks like:
public class TestingUtils {
public static void setFieldValue( Object object, String fieldName, Object value ) {
Field field = ReflectionUtils.findField( object.getClass(), fieldName );
ReflectionUtils.makeAccessible( field );
ReflectionUtils.setField( field, object, value );
}
}
All credits goes to the author of the blog.
Spring Boot here. I currently have the following REST controller:
#RestController
public class FizzbuzzController {
private final FizzbuzzService FizzbuzzService;
public FizzbuzzController(FizzbuzzService FizzbuzzService) {
this.FizzbuzzService = FizzbuzzService;
}
#PostMapping("/Fizzbuzzs/{fizzbuzzId}")
public ResponseEntity<FizzbuzzDTO> addFizzbuzz(#RequestParam("files") List<MultipartFile> files,
#PathVariable String fizzbuzzId) throws IOException {
FizzbuzzDTO fizzbuzzDTO = fizzbuzzService.store(files, fizzbuzzId);
return ResponseEntity.status(HttpStatus.OK).body(fizzbuzzDTO);
}
}
I would like to write an integration test for it that:
Mocks or stubs an HTTP request to the URL; and
Allows me to inject the FizzbuzzController (under test) with a mocked FizzbuzzService or the real thing; and
Allows me to inspect the HTTP response coming back from the method (check status code, check response entity, etc.)
My best attempt thus far:
#WebMvcTest(FizzbuzzController.class)
#EnableConfigurationProperties
#AutoConfigureMockMvc
public class FizzbuzzControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private FizzbuzzService FizzbuzzService;
#Test
public void should_store_fizzbuzz_files() throws Exception {
// I can't even get the test to run
assertTrue(1 == 1);
}
}
When I run this, the test fails to run and it is clear (looking at the logs) that Spring is loading the entire application context of my app, whereas I just want it to isolate the context to this test class, the main FizzbuzzController class, and anything in the dependency tree underneath it.
Can anyone spot where I'm going awry?
You need another context for testing. I'm suggesting you to have a separate Test config:
#TestConfiguration
#Slf4j
#EnableJpaRepositories("tth.patientportal.repository")
public class TestConfig { // bean configs goes here for testing if you need to change
// context}
and in a controller test build the context like below:
#RunWith(SpringRunner.class)
#AutoConfigureTestEntityManager
#SpringBootTest
#TestPropertySource("classpath:application-unittest.properties")
#ContextConfiguration(classes = {TestConfig.class})
public class RestControllerTest {
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
#Before
public void setup()
{
mockMvc = MockMvcBuilders.
webAppContextSetup(webApplicationContext)
.build();
}
#Test
public void shouldReturnRegisteredUser() throws Exception {
this.mockMvc.
perform(MockMvcRequestBuilders
.post("url")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.username").exists());
}
}
I have a service that has a DataProvider which I want to mock.
Problem: the service uses the data provider in #PostConstruct. But when I use #MockBean, the mocked values are not jet present in #PostConstruct.
What could I do?
#Service
public class MyService {
private List<Object> data;
#Autowired
private DataProvider dataProvider;
#PostConstruct
public void initData() {
data = dataProvider.getData();
}
public void run() {
System.out.println(data); //always null in tests
}
}
#SpringBootTest
public class Test {
#MockBean
private DataProvider dataProvider;
#Test
public void test() {
when(dataProvider.getData()).thenReturn(mockedObjects);
//dataProvider.init(); //this fixes it, but feels wrong
service.run();
}
}
IMHO unit testing MyService would be a better solution for this particular scenario (and I wouldn't feel wrong about calling initService manually in that case), but if you insist...
You could simply override the DataProvider bean definition for this particular test and mock it beforehand, sth like:
#SpringBootTest(classes = {MyApplication.class, Test.TestContext.class})
public class Test {
#Test
public void test() {
service.run();
}
#Configuration
static class TestContext {
#Primary
public DataProvider dataProvider() {
var result = Mockito.mock(DataProvider.class);
when(result.getData()).thenReturn(mockedObjects);
return result;
}
}
}
You might need to set spring.main.allow-bean-definition-overriding to true for the above to work.
I got 2 modules User and Email, both of them have 1 entry point which is a facade, rest is package scoped. The configuration is done in 2 classes
#Configuration
class UserConfiguration {
#Bean
UserFacade userFacade(UserRepository repository, EmailFacade emailFacade) {
return new UserFacade(repository, emailFacade);
}
}
#Configuration
class EmailConfiguration {
#Bean
EmailFacade emailFacade(EmailSender emailSender) {
return new EmailFacade(emailSender);
}
}
Now, I want to write tests that don't require Spring to start. I implemented a simple InMemoryRepository to make this happen
#RunWith(MockitoJUnitRunner.class)
public class RegisterUserTest {
#Mock
private EmailFacade emailFacade = new EmailFacade(new FakeEmailSender());
#InjectMocks
private UserFacade userFacade = new UserConfiguration().userFacade(new InMemoryUserRepository(), emailFacade);
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
}
I need some fake objects to instantiate EmailFacade so I wrote fake implementation
public class FakeEmailSender implements EmailSender {
#Override
public void sendEmail(EmailMessage emailMessage) throws RuntimeException {
}
}
In that scenario, I'm testing User domain, so I want to mock Email anyways.
I wrote a test to check if it works
#Test
public void shouldReturnSendingFailed() {
Mockito.when(emailFacade.sendUserVerificationEmail(Mockito.any())).thenReturn(Either.left(EmailError.SENDING_FAILED));
assertThat(userFacade.registerNewUser(RegisterUserDto.builder()
.username(USERNAME_4)
.email(VALID_EMAIL)
.password(VALID_PASSWORD).build()).getLeft(), is(EmailError.SENDING_FAILED));
}
But it isn't... after running this test I got
java.util.NoSuchElementException: getLeft() on Right
edit#
regiserNewUser() method
Either<DomainError, SuccessMessage> register(RegisterUserDto registerUserDto) {
if(userRepository.findUser(registerUserDto.getUsername()).isPresent())
return Either.left(UserError.USERNAME_ALREADY_EXISTS);
var userCreationResult = User.createUser(registerUserDto);
var savedUser = userCreationResult.map(this::saveUser);
var emailDto = savedUser.map(this::createVerificationEmail);
return emailDto.isRight() ? emailFacade.sendUserVerificationEmail(emailDto.get())
: Either.left(emailDto.getLeft());
}
Edit2#
With following test configuration
#RunWith(MockitoJUnitRunner.class)
public class RegisterUserTest {
#Mock
private EmailFacade emailFacade;
#InjectMocks
private UserFacade userFacade = new UserConfiguration().userFacade(new InMemoryUserRepository(), emailFacade);
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
}
I got nullpointer here, last line of registerNewUser().
Try running this code
#RunWith(MockitoJUnitRunner.class)
public class RegisterUserTest {
#Mock
private EmailFacade emailFacade;
private UserFacade userFacade;
#Before
public void setUp() {
userFacade = new UserConfiguration().userFacade(new InMemoryUserRepository(), emailFacade);
}
}
There are a few issues with your code:
You initialize your mocks twice. You don’t need to call initMocks in the setUp method if you are using Mockito runner
You are trying to inject mocks to already initialized object. But the field you are trying to inject is also passed to the constructor. Please read #InjectMocks doc, to check the strategies used to inject the mocks:
constructor (not used here, already initialized object)
setter (do you have one?)
field (is it not final)
There are details to each strategy (see my questions above). If no staregy is matched, Mockito will fail silently. The fact that you are passing an object in constructor, and rely on setter or field injection afterwards makes this code unnecesarily complex.
Trying to write an integration test for a Spring application. Say i've got a class A which contains a class B object. Class B contains a class C object and I need to mock an object within this class for the integration test - any idea how i go about doing that without passing every object through as a parameter in the constructor?
e.g.
#Service
Class A {
#Autowired
private B b;
public void testA() {
B.testB();
}
}
#Service
Class B {
#Autowired
private C c;
public void testB() {
c.testC();
}
}
#Service
Class C {
//External class pulled in from dependency library
#Autowired
private RestTemplate restTemplate;
public void testC() {
restTemplate.doSomethingInOutsideWorld();
}
}
Integration test:
#RunWith(JUnitParamsRunner.class)
#SpringBootTest
public class MyIt {
#ClassRule
public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();
#Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
#Mock
private RestTemplate restTemplate;
#Autowired
private A a;
#InjectMocks
private C c;
#Before
public void setup() {
initMocks(this);
}
#Test
public void test1() throws IOException {
a.testA()
}
}
Doesn't mock the RestTemplate object, it tries to hit the outside world. Any advice on how to resolve this?
Achieve this by using SpringRunner and #MockBean
#RunWith(SpringRunner.class) is used to provide a bridge between Spring Boot test features and JUnit. Whenever we are using any Spring Boot testing features in out JUnit tests, this annotation will be required.
The #SpringBootTest annotation can be used when we need to bootstrap the entire container. The annotation works by creating the ApplicationContext that will be utilized in our tests.
Annotation that can be used to add mocks to a Spring ApplicationContext. Can be used as a class level annotation or on fields in either #Configuration classes, or test classes that are #RunWith the SpringRunner.
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyIt {
#MockBean
private RestTemplate restTemplate;
#Autowired
private A a;
#Before
public void setup() {
initMocks(this);
}
#Test
public void test1() throws IOException {
given(this.restTemplate.doSomethingInOutsideWorld()).willReturn(custom object);
a.testA()
}
}