#I try to write JUnit integration test and to test whether service method works well. It isn't.
163 messages ยท Page 1 of 1 (latest)
โ This post has been reserved for your question.
Hey @thorn basalt! Please use
/closeor theClose Postbutton above when your problem is solved. Please remember to follow the help guidelines. This post will be automatically closed after 300 minutes of inactivity.
TIP: Narrow down your issue to simple and precise questions to maximize the chance that others will reply in here.
@Service
public class ValidateCaptcha {
private static final Logger LOG = LoggerFactory.getLogger(ValidateCaptcha.class);
private final RestTemplate template;
@Value("${google.recaptcha.verification.endpoint}")
String recaptchaEndpoint;
@Value("${google.recaptcha.secret}")
String recaptchaSecret;
public ValidateCaptcha(final RestTemplateBuilder templateBuilder) {
this.template = templateBuilder.build();
}
/**
* Method to validate the captcha response coming from the client
* and to return either true or false after the validation.
* Reference url - https://developers.google.com/recaptcha/docs/verify
* @param captchaResponse
* @return boolean
*/
public boolean validateCaptcha(final String captchaResponse) {
LOG.info("RestTemplate instance: {}", template);
LOG.info("Going to validate the captcha response = [{}]", captchaResponse);
final MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
// "secret" is a required param and it represents the shared key between your site and the recaptcha.
params.add("secret", recaptchaSecret);
// "response" is a required param and it represents the user token provided
// by the recaptcha client-side integration on your site.
params.add("response", captchaResponse);
CaptchaResponse apiResponse = null;
try {
apiResponse = template.postForObject(recaptchaEndpoint, params, CaptchaResponse.class);
} catch (final RestClientException e) {
LOG.error("Some exception occurred while binding to the recaptcha endpoint.\n[{}]", e);
}
if (Objects.nonNull(apiResponse) && apiResponse.isSuccess()) {
LOG.info("Captcha API response = [{}]", apiResponse.toString());
return true;
} else {
return false;
}
}
}
This is the test:
@ExtendWith(MockitoExtension.class)
@SpringBootTest
public class ValidateCaptchaTest {
@Mock
private RestTemplate restTemplate;
@Autowired
private ValidateCaptcha validateCaptcha;
@Test
public void testValidateCaptcha() {
String captchaResponse = "test-captcha-response";
CaptchaResponse mockResponse = new CaptchaResponse();
mockResponse.setSuccess(true); // Set success or failure based on your test scenario
when(restTemplate.postForObject(
anyString(), // Mock any URL (recaptchaEndpoint)
any(MultiValueMap.class), // Mock any request params
eq(CaptchaResponse.class) // Expect a response of type CaptchaResponse
)).thenReturn(mockResponse);
boolean result = validateCaptcha.validateCaptcha(captchaResponse);
// Debug output to see what's happening
System.out.println("Captcha API call result: " + result);
assertTrue(result); // or assertFalse depending on mockResponse success
}
}
Console output:
Am I doing anything wrong in the test? Or it cannot be tested like this?
The application works well. When requesting from the frontend with the specific captchaResponse string the method returns true.
But I want to test this method like a JUnit test.
It should assert to true, but it is false. Why? I though mocked rest template already set the response.
Looks like the service method is invoked for real. Should it be like this?
I try to write JUnit integration test and to test whether service method works well. It isn't.
you are not injecting resttemplate in your service class
Isn't injected via constructor?
The builder is imported import org.springframework.boot.web.client.RestTemplateBuilder; I don't know its implementation
change the constructor to resttemplate instead
You mean:
//...
private final RestTemplate template;
//...
public ValidateCaptcha(final RestTemplateBuilder templateBuilder) {
this.template = templateBuilder.build();
}
public ValidateCaptcha() {
this.template = new RestTemplate();
}
two constructors?
no only one
or to remove the one that us using the builder?
yes modify to take in resttemplate
The same:
public ValidateCaptcha(final RestTemplate template) { // <--- you inject this
this.template = template;
}
ahh
lol
Now I get the exception:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in it.akademija.recaptcha.ValidateCaptcha required a bean of type 'org.springframework.web.client.RestTemplate' that could not be found.
The injection point has the following annotations:
- @org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'org.springframework.web.client.RestTemplate' in your configuration.
2024-09-13 19:12:01.991 ERROR 10580 --- [ main] o.s.test.context.TestContextManager : Caught exception while allowing TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener@60f7cc1d] to prepare test instance [it.akademija.recaptcha.ValidateCaptchaTest@7901a5ab]
java.lang.IllegalStateException: Failed to load ApplicationContext
//...
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'validateCaptcha' defined in file [G:\Java_programavimas\Mokyklos-projektas\Project\Maitinimas-back\target\classes\it\akademija\recaptcha\ValidateCaptcha.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.web.client.RestTemplate' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
Should I add the bean to configuration?
the rest tamplate
lets see
@InjectMocks
private ValidateCaptcha validateCaptcha;
Same:
In order the application to run I had to add the bean to configuration class:
@Bean
public RestTemplate restTemplateBean() {
return new RestTemplate();
}
From frontend the reCapture works:
Maybe the service method cannot be tested like this?
It is calling to the public URL for real.
But in mock case it shouldn't.
Strange
@BeforeEach // replace with junit5 equivalent
void setUp() {
MockitoAnnotations.initMocks(this);
validateCaptcha= new ValidateCaptcha (restTemplate);
}
its because you are not injecting mock
you need to inject mock for it to work
JUnit testing is not my strong side. I do it rarely.
Ok now I get error:
org.mockito.exceptions.misusing.PotentialStubbingProblem:
Strict stubbing argument mismatch. Please check:
- this invocation of 'postForObject' method:
restTemplate.postForObject(
null,
{"secret" = [null], "response" = [test-captcha-response]},
class it.akademija.recaptcha.CaptchaResponse
);
-> at it.akademija.recaptcha.ValidateCaptcha.validateCaptcha(ValidateCaptcha.java:57)
- has following stubbing(s) with different arguments:
1. restTemplate.postForObject("", null, null);
-> at it.akademija.recaptcha.ValidateCaptchaTest.testValidateCaptcha(ValidateCaptchaTest.java:46)
Typically, stubbing argument mismatch indicates user mistake when writing tests.
Mockito fails early so that you can debug potential problem easily.
However, there are legit scenarios when this exception generates false negative signal:
- stubbing the same method multiple times using 'given().will()' or 'when().then()' API
Please use 'will().given()' or 'doReturn().when()' API for stubbing.
- stubbed method is intentionally invoked with different arguments by code under test
Please use default or 'silent' JUnit Rule (equivalent of Strictness.LENIENT).
For more information see javadoc for PotentialStubbingProblem class.
at it.akademija.recaptcha.ValidateCaptcha.validateCaptcha(ValidateCaptcha.java:57)
at it.akademija.recaptcha.ValidateCaptchaTest.testValidateCaptcha(ValidateCaptchaTest.java:52)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
I guess i should provide the secret
I should add more things
What are those stubbings? The parameters for restTemplate.postForObject( ? I have empty string and null for the second and third parameters? And it asks that I would give right type parameters and not nulls?
I will try it.
@MockitoSettings(strictness = Strictness.LENIENT)
try adding this annotation to your test class
ok.. hold on
You mean like this:
@MockitoSettings(strictness = Strictness.LENIENT)
@ExtendWith(MockitoExtension.class)
@SpringBootTest
public class ValidateCaptchaTest {
?
yes
It's already 2024-09-13 19:55:25.955 INFO 10884 --- [ main] it.akademija.recaptcha.ValidateCaptcha : RestTemplate instance: restTemplate
earlier it was: 2024-09-13 18:32:18.109 INFO 1392 --- [ main] it.akademija.recaptcha.ValidateCaptcha : RestTemplate instance: org.springframework.web.client.RestTemplate@2db0dd19
wait let me try to reproduce it at my end
when(restTemplate.postForObject(
anyString(), // Mock any URL (recaptchaEndpoint)
any(MultiValueMap.class), // Mock any request params
eq(CaptchaResponse.class) // Expect a response of type CaptchaResponse
)).thenReturn(mockResponse);
can you provide me import for eq here?
import static org.mockito.ArgumentMatchers.eq;
Or do you need CaptchaResponse class?
this
this, - I understand like the import string import static org.mockito.ArgumentMatchers.eq;. Ok
ok so this works
@ExtendWith(MockitoExtension.class)
class HttpBinTest {
@Mock
RestTemplate restTemplate;
@Autowired
private HttpBin validateCaptcha;
@BeforeEach
// replace with junit5 equivalent
void setUp() {
MockitoAnnotations.initMocks(this);
validateCaptcha = new HttpBin(restTemplate);
}
@Test
void validateCaptcha() {
when(restTemplate.postForObject(
anyString(), // Mock any URL (recaptchaEndpoint)
any(Object.class), // Mock any request params
eq(String.class // Expect a response of type CaptchaResponse
))).thenReturn("world");
System.out.println(validateCaptcha.validateCaptcha("hello"));
}
}
without SpringBootTest... I thought so..
I removed spring crap and just manually added mock resttemplate
ok, testing
No. it's still false:
@MockitoSettings(strictness = Strictness.LENIENT)
@ExtendWith(MockitoExtension.class)
public class ValidateCaptchaTest {
@Mock
private RestTemplate restTemplate;
@Autowired
private ValidateCaptcha validateCaptcha;
@SuppressWarnings("deprecation")
@BeforeEach // replace with junit5 equivalent
void setUp() {
MockitoAnnotations.initMocks(this);
validateCaptcha= new ValidateCaptcha (restTemplate);
}
@Test
public void testValidateCaptcha() {
String captchaResponse = "test-captcha-response";
CaptchaResponse mockResponse = new CaptchaResponse();
mockResponse.setSuccess(true); // Set success or failure based on your test scenario
when(restTemplate.postForObject(
anyString(), // Mock any URL (recaptchaEndpoint)
any(MultiValueMap.class), // Mock any request params
eq(CaptchaResponse.class) // Expect a response of type CaptchaResponse
)).thenReturn(mockResponse);
boolean result = validateCaptcha.validateCaptcha(captchaResponse);
// Debug output to see what's happening
System.out.println("Captcha API call result: " + result);
assertTrue(result); // or assertFalse depending on mockResponse success
}
}
add a breakpoint
if (Objects.nonNull(apiResponse) && apiResponse.isSuccess()) {
LOG.info("Captcha API response = [{}]", apiResponse.toString());
return true;
}
and check value for apiResponse
I never debuugged. teach me. I added the break point and launcged the test in debugging mode. Howered I don't know where to look for the apiResponse value:
there should be a variables window somewhere in right
that panel shows all variables in that
hmm i see, expand this and see id for resttemplate
any(MultiValueMap.class), change this to any(Object.class)
yes remove @MockitoSettings(strictness = Strictness.LENIENT) as well
we need those errors
I will test it in debug mode like this now:
stubbing
org.mockito.exceptions.misusing.PotentialStubbingProblem:
Strict stubbing argument mismatch. Please check:
- this invocation of 'postForObject' method:
restTemplate.postForObject(
null,
{"secret" = [null], "response" = [test-captcha-response]},
class it.akademija.recaptcha.CaptchaResponse
);
-> at it.akademija.recaptcha.ValidateCaptcha.validateCaptcha(ValidateCaptcha.java:57)
- has following stubbing(s) with different arguments:
1. restTemplate.postForObject("", null, null);
-> at it.akademija.recaptcha.ValidateCaptchaTest.testValidateCaptcha(ValidateCaptchaTest.java:48)
Typically, stubbing argument mismatch indicates user mistake when writing tests.
Mockito fails early so that you can debug potential problem easily.
However, there are legit scenarios when this exception generates false negative signal:
- stubbing the same method multiple times using 'given().will()' or 'when().then()' API
Please use 'will().given()' or 'doReturn().when()' API for stubbing.
- stubbed method is intentionally invoked with different arguments by code under test
Please use default or 'silent' JUnit Rule (equivalent of Strictness.LENIENT).
For more information see javadoc for PotentialStubbingProblem class.
at it.akademija.recaptcha.ValidateCaptcha.validateCaptcha(ValidateCaptcha.java:57)
at it.akademija.recaptcha.ValidateCaptchaTest.testValidateCaptcha(ValidateCaptchaTest.java:54)
I guess I needto provide URL string, Multivalue map parameters
do you have equals and hashcode in CaptchaResponse ?
No.
public class CaptchaResponse {
boolean success;
LocalDateTime challenge_ts;
String hostname;
@JsonProperty("error-codes")
List<String> errorCodes;
public CaptchaResponse() {}
public CaptchaResponse(boolean success, LocalDateTime challenge_ts, String hostname, List<String> errorCodes) {
super();
this.success = success;
this.challenge_ts = challenge_ts;
this.hostname = hostname;
this.errorCodes = errorCodes;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public LocalDateTime getChallenge_ts() {
return challenge_ts;
}
public void setChallenge_ts(LocalDateTime challenge_ts) {
this.challenge_ts = challenge_ts;
}
public String getHostname() {
return hostname;
}
public void setHostname(String hostname) {
this.hostname = hostname;
}
public List<String> getErrorCodes() {
return errorCodes;
}
public void setErrorCodes(List<String> errorCodes) {
this.errorCodes = errorCodes;
}
@Override
public String toString() {
return "CaptchaResponse [success=" + success + ", challenge_ts=" + challenge_ts + ", hostname=" + hostname
+ ", errorCodes=" + errorCodes + "]";
}
}
implement and try
generated:
@Override
public int hashCode() {
return Objects.hash(challenge_ts, errorCodes, hostname, success);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CaptchaResponse other = (CaptchaResponse) obj;
return Objects.equals(challenge_ts, other.challenge_ts) && Objects.equals(errorCodes, other.errorCodes)
&& Objects.equals(hostname, other.hostname) && success == other.success;
}
trying
Same:
org.mockito.exceptions.misusing.PotentialStubbingProblem:
Strict stubbing argument mismatch. Please check:
- this invocation of 'postForObject' method:
restTemplate.postForObject(
null,
{"secret" = [null], "response" = [test-captcha-response]},
class it.akademija.recaptcha.CaptchaResponse
);
-> at it.akademija.recaptcha.ValidateCaptcha.validateCaptcha(ValidateCaptcha.java:57)
- has following stubbing(s) with different arguments:
1. restTemplate.postForObject("", null, null);
-> at it.akademija.recaptcha.ValidateCaptchaTest.testValidateCaptcha(ValidateCaptchaTest.java:48)
Typically, stubbing argument mismatch indicates user mistake when writing tests.
Mockito fails early so that you can debug potential problem easily.
However, there are legit scenarios when this exception generates false negative signal:
- stubbing the same method multiple times using 'given().will()' or 'when().then()' API
Please use 'will().given()' or 'doReturn().when()' API for stubbing.
- stubbed method is intentionally invoked with different arguments by code under test
Please use default or 'silent' JUnit Rule (equivalent of Strictness.LENIENT).
For more information see javadoc for PotentialStubbingProblem class.
at it.akademija.recaptcha.ValidateCaptcha.validateCaptcha(ValidateCaptcha.java:57)
at it.akademija.recaptcha.ValidateCaptchaTest.testValidateCaptcha(ValidateCaptchaTest.java:54)
it is, thats the problem...bruh
checking.. wait a moment
I thing it is that first parameter:
when(restTemplate.postForObject(
anyString(), // Mock any URL (recaptchaEndpoint)
any(MultiValueMap.class), // Mock any request params
eq(CaptchaResponse.class) // Expect a response of type CaptchaResponse
)).thenReturn(mockResponse);
the anyString().
nope, your service class
you are injecting it
and its null
just set some value temporarily
and then test
during runtime it is taken from environment:
@Value("${google.recaptcha.verification.endpoint}")
String recaptchaEndpoint;
in application.properties it is: google.recaptcha.verification.endpoint=https://www.google.com/recaptcha/api/siteverify
So how to set it up?
IS this ok:
yep
what?
in test class, due to constructor change I have to add the second parameter
Right?
ok..
thats whats tests are for
just think...
any string will work
we dont invoke that url at all
but yeah actual url will also work
Debug:
resume
finally, lol
Is the test correct without launchig the Spring Boot container
I have also successful controller test, So I reckon that yes
@SpringBootTest
@AutoConfigureMockMvc
class RecaptureControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private ValidateCaptcha validateCaptcha;
@Test
void whenValidCaptcha_thenReturnTrue() throws Exception {
// Mocking the service call
Mockito.when(validateCaptcha.validateCaptcha(Mockito.anyString())).thenReturn(true);
RecaptchaDTO dto = new RecaptchaDTO("testCaptchaResponse");
mockMvc.perform(post("/api/verify")
.contentType(MediaType.APPLICATION_JSON)
.content(new ObjectMapper().writeValueAsString(dto)))
.andExpect(status().isOk())
.andExpect(content().string("true")); // Expecting true for valid captcha
}
@Test
void whenInvalidCaptcha_thenReturnFalse() throws Exception {
// Mocking the service call
Mockito.when(validateCaptcha.validateCaptcha(Mockito.anyString())).thenReturn(false);
RecaptchaDTO dto = new RecaptchaDTO("invalidCaptchaResponse");
mockMvc.perform(post("/api/verify")
.contentType(MediaType.APPLICATION_JSON)
.content(new ObjectMapper().writeValueAsString(dto)))
.andExpect(status().isOk())
.andExpect(content().string("false")); // Expecting false for invalid captcha
}
}
this one works
btw you can start to add spring back now
what?
yeah we got the root issue for why mockito was'nt working right the url was null
yea
you mea @SpringBootTest?
yeah
ok.. hold on
you're welcome