Home > Enterprise >  How to use @SpyBean in a class that receives arguments by the constructor?
How to use @SpyBean in a class that receives arguments by the constructor?

Time:07-02

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.

CodePudding user response:

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.

  • Related