#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)

thorn basalt
#

This is the service class:

civic briarBOT
#

โŒ› This post has been reserved for your question.

Hey @thorn basalt! Please use /close or the Close Post button 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.

thorn basalt
#
@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
    }
}
#

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.

dense vapor
#

you are not injecting resttemplate in your service class

thorn basalt
#

Isn't injected via constructor?

dense vapor
#

yes but your constructor takes in a builer

#

you do not have a builder

thorn basalt
#

The builder is imported import org.springframework.boot.web.client.RestTemplateBuilder; I don't know its implementation

dense vapor
#

change the constructor to resttemplate instead

thorn basalt
#

You mean:

//...
private final RestTemplate template;
//...
    public ValidateCaptcha(final RestTemplateBuilder templateBuilder) {
        this.template = templateBuilder.build();
    }
    
    public ValidateCaptcha() {
        this.template = new RestTemplate();
    }

two constructors?

dense vapor
#

no only one

thorn basalt
#

or to remove the one that us using the builder?

dense vapor
#

yes modify to take in resttemplate

thorn basalt
#

The same:

dense vapor
#
 public ValidateCaptcha(final RestTemplate template) { // <--- you inject this
        this.template = template;
    }
thorn basalt
#

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

dense vapor
#
@InjectMocks 
private ValidateCaptcha validateCaptcha;
thorn basalt
#

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

dense vapor
#
@BeforeEach   // replace with junit5 equivalent
void setUp() {
    MockitoAnnotations.initMocks(this);
    validateCaptcha= new ValidateCaptcha (restTemplate);
}
dense vapor
#

you need to inject mock for it to work

thorn basalt
#

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.

dense vapor
#

@MockitoSettings(strictness = Strictness.LENIENT)
try adding this annotation to your test class

thorn basalt
#

ok.. hold on

#

You mean like this:

@MockitoSettings(strictness = Strictness.LENIENT)
@ExtendWith(MockitoExtension.class)
@SpringBootTest
public class ValidateCaptchaTest {

?

dense vapor
#

yes

thorn basalt
#

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

dense vapor
#

wait let me try to reproduce it at my end

thorn basalt
#

Should I give you the project?

#

Ok. will wait

dense vapor
#

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?

thorn basalt
#
import static org.mockito.ArgumentMatchers.eq;

Or do you need CaptchaResponse class?

dense vapor
#

this

thorn basalt
#

this, - I understand like the import string import static org.mockito.ArgumentMatchers.eq;. Ok

dense vapor
#

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"));
    }
}
thorn basalt
#

without SpringBootTest... I thought so..

dense vapor
#

I removed spring crap and just manually added mock resttemplate

thorn basalt
#

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
    }
}
dense vapor
#

add a breakpoint

if (Objects.nonNull(apiResponse) && apiResponse.isSuccess()) {
           LOG.info("Captcha API response = [{}]", apiResponse.toString());
           return true;
        }
#

and check value for apiResponse

thorn basalt
#

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:

dense vapor
#

there should be a variables window somewhere in right

#

that panel shows all variables in that

thorn basalt
#

it's null

dense vapor
#

hmm i see, expand this and see id for resttemplate

thorn basalt
#

I run it once more when showing latest image

dense vapor
#

any(MultiValueMap.class), change this to any(Object.class)

thorn basalt
#

It was already Object:

#

I will change to MultiValueMap.class

#

ok?

dense vapor
#

yes remove @MockitoSettings(strictness = Strictness.LENIENT) as well

#

we need those errors

thorn basalt
#

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

dense vapor
#

do you have equals and hashcode in CaptchaResponse ?

thorn basalt
#

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 + "]";
    }

}
dense vapor
#

implement and try

thorn basalt
#

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)
dense vapor
#

is your recaptchaEndpoint null?

#

it should be string

dense vapor
thorn basalt
#

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().

dense vapor
#

nope, your service class

#

you are injecting it

#

and its null

#

just set some value temporarily

#

and then test

thorn basalt
#

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

dense vapor
#

yes but we dont have spring here, so it is just null

#

thats causing the issue

thorn basalt
#

So how to set it up?

dense vapor
#

always constructor inject

#

just like resttemplate

thorn basalt
#

IS this ok:

dense vapor
#

yep

thorn basalt
#

ok will test

#

and it test I need to write it too

dense vapor
#

what?

thorn basalt
#

in test class, due to constructor change I have to add the second parameter

#

Right?

dense vapor
#

yes... and

#

well run and check

thorn basalt
#

ok..

dense vapor
#

thats whats tests are for

thorn basalt
#

same error

#

damn it

dense vapor
#

again we removed spring that @value wont work

#

what do you put there instead?

thorn basalt
#

I will put the actual URL

#

the "https://www.google.com/recaptcha/api/siteverify"

dense vapor
#

just think...

#

any string will work

#

we dont invoke that url at all

#

but yeah actual url will also work

thorn basalt
#

Debug:

dense vapor
#

resume

thorn basalt
#

And test:

#

๐Ÿ™‚

#

Thanks

dense vapor
#

finally, lol

thorn basalt
#

Is the test correct without launchig the Spring Boot container

dense vapor
#

you tell me, does it test business logic?

#

that you intended to test?

thorn basalt
#

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

dense vapor
#

btw you can start to add spring back now

thorn basalt
dense vapor
#

yeah we got the root issue for why mockito was'nt working right the url was null

dense vapor
#

now you can just add spring back

#

fix url issue

thorn basalt
#

you mea @SpringBootTest?

dense vapor
#

yeah

thorn basalt
#

ok.. hold on

dense vapor
thorn basalt
#

Thank you, buddy.

dense vapor
#

you're welcome