Issue
I'm creating some unit tests for a Spring Boot application.
In a class called ComicService there is a method called getComicByApi and I want to create a test for that method, but this method accesses another method of the same class called getHash.
I need to configure the behavior of getHash, so I used the @SpyBean annotation in creating the ComicService object.
The problem is that when running the test it gives an error in the part where I use Mockito.when().thenReturn() to configure the behavior of getHash.
I found that the error is related to the fact that I use @BeforeEach public void setUp() to instantiate the annotated class with @SpyBean passing its constructor arguments, but I still don't know how to solve it.
Does anyone know how to solve this problem?
ComicService
@Service
public class ComicService {
private String publicKey;
private String privateKey;
private MarvelClient marvelClient;
public ComicService(@Value("${marvel.public_key}")String publicKey,
@Value("${marvel.private_key}") String privateKey, MarvelClient marvelClient) {
this.publicKey = publicKey;
this.privateKey = privateKey;
this.marvelClient = marvelClient;
}
public MarvelAPIModelDTO getComicByApi(Integer idComicMarvel) {
String timeStamp = String.valueOf((int)(System.currentTimeMillis() / 1000));
String hash = getHash(timeStamp);
MarvelAPIModelDTO comic = marvelClient.getComic(idComicMarvel, timeStamp, timeStamp, hash);
return comic;
}
public String getHash(String timeStemp) {
String value = timeStemp+privateKey+publicKey;
MessageDigest md;
try {
md = MessageDigest.getInstance("MD5");
} catch(NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
BigInteger hash = new BigInteger(1, md.digest(value.getBytes()));
return hash.toString(16);
}
}
ComicServiceTest
@ExtendWith(SpringExtension.class)
@ActiveProfiles("test")
public class ComicServiceTest {
@SpyBean
ComicService comicService;
@MockBean
MarvelClient marvelClient;
@BeforeEach
public void setUp() {
this.comicService = new ComicService("ae78641e8976ffdf3fd4b71254a3b9bf", "eb9fd0d8r8745cd0d554fb2c0e7896dab3bb745", marvelClient);
}
@Test
public void getComicByApiTest() {
// Scenario
MarvelAPIModelDTO foundMarvelAPIModelDTO = createMarvelAPIModelDTO();
//It's giving an error on this line
Mockito.when(comicService.getHash(Mockito.anyString())).thenReturn("c6fc42667498ea8081a22f4570b42d03");
Mockito.when(marvelClient.getComic(Mockito.anyInt(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString())).thenReturn(foundMarvelAPIModelDTO);
// Execution
MarvelAPIModelDTO marvelAPIModelDTO = comicService.getComicByApi(1);
// Verification
Assertions.assertThat(marvelAPIModelDTO.getData().getResults().get(0).getId()).isEqualTo(1);
}
}
Error
at com.gustavo.comicreviewapi.services.ComicServiceTest.getComicByApiTest(ComicServiceTest.java:58)
You cannot use argument matchers outside of verification or stubbing.
Examples of correct usage of argument matchers:
when(mock.get(anyInt())).thenReturn(null);
doThrow(new RuntimeException()).when(mock).someVoidMethod(any());
verify(mock).someMethod(contains("foo"))
This message may appear after an NullPointerException if the last matcher is returning an object
like any() but the stubbed method signature expect a primitive argument, in this case,
use primitive alternatives.
when(mock.get(any())); // bad use, will raise NPE
when(mock.get(anyInt())); // correct usage use
Also, this error might show up because you use argument matchers with methods that cannot be mocked.
Following methods *cannot* be stubbed/verified: final/private/equals()/hashCode().
Mocking methods declared on non-public parent classes is not supported.
Solution
It looks like @SpyBean
is unable to find instance of your service class. Quick alternative for this is just remove @SpyBean
from ComicService comicService;
& do following in your @BeforeEach
:
@BeforeEach
public void setUp() {
this.comicService = Mockito.spy(new ComicService("ae78641e8976ffdf3fd4b71254a3b9bf", "eb9fd0d8r8745cd0d554fb2c0e7896dab3bb745", marvelClient));
}
Here, you are creating spy & then using it inside your test class.
Answered By - Ashish Patil
Answer Checked By - Mary Flores (JavaFixing Volunteer)