如何在接收构造函数参数的类中使用@spybean?

发布于 2025-02-12 08:21:53 字数 4012 浏览 3 评论 0原文

我正在为Spring Boot应用程序创建一些单元测试。

在称为 comicservice 的类中,有一种称为 getcomicbyapi 的方法,我想为该方法创建一个测试,但是此方法访问了同一类的另一种方法,称为 Gethash

我需要配置Gethash的行为,因此我使用 @spybean 注释来创建 comicservice 对象。

问题在于,在运行测试时,它在我使用mockito.when()。thenreturn()的部分中会出现一个错误来配置Gethash的行为。

我发现该错误与我使用 @beforeach public void setup()使用@spybean通过其构造函数论证实例化的班级有关,但我仍然不知道如何解决,它。

有人知道如何解决这个问题吗?

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);        
    }

}

错误

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.

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

何以畏孤独 2025-02-19 08:21:53

看起来@spybean无法找到您服务类的实例。快速替代方案是从comicservice comicservice;&在您的@beforeeach中进行关注:

@BeforeEach
    public void setUp() {
        this.comicService = Mockito.spy(new ComicService("ae78641e8976ffdf3fd4b71254a3b9bf", "eb9fd0d8r8745cd0d554fb2c0e7896dab3bb745", marvelClient));

    }

在这里,您正在创建间谍&然后在测试课内使用它。

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.

蓝眼泪 2025-02-19 08:21:53

您可以使用, @oildbean注释@spybean,可以使用mockito.when()。thencalleRealMethod(),您需要调用真实方法和mockito.when()。 。这样,在这种情况下,必须摆脱您的所有问题,您可以按照自己的意愿进行嘲笑。

例如:

@Component
class MyClass {

public String firstMethod(String value){
    return value;
}

public void secondMethod(String value){
    some code
}

@Component
class Main {
    @Autowire
    private MyClass clazz;
    
    public String getFirstMethod(String value){
        some code;
    }

    public void getSecondMethod(String value){
        clazz.secondMethod(value);
    }
}

在测试课中,您必须在下面声明班级的模拟豆子,并根据需要嘲笑它们。相反,您可以使用thenAnswer(),thenthrows(),(),而不是donothing()用于void方法,可以使用doanswer(),dothrow(),dothrow(),doreturn(),

@MockBean(classes={MyClass.class})
public MainTest {

@Autowire
private Main main
@Autowire
privat MyClass clazz;

    @Test
    void getFirstMethod_mock(){
        String value = "something";
        when(clazz.firstMethod(anyString()).thenReturn("some string value");
        
        String actual = main.getFirstMethod(value );

        assertEquals("some string value", actual);
    
    }

    @Test
    void getFirstMethod_realMethod(){
        String value = "something";
        when(clazz.firstMethod(anyString()).thenCallRealMethod();
        
        String actual = main.getFirstMethod(value );

        assertEquals(realResultOfFirstMethod, actual);
    
    }

    @Test
    void getSecondMethod_mock(){
        String value = "something";
        doNothing().when(clazz).secondMethod(anyString());
        
        main.etSecondMethod(value );
    
    }

    @Test
    void getSecondMethod_realMethod(){
        String value = "something";
        doCallRealMethod().when(clazz).secondMethod(anyString());
        
        main.etSecondMethod(value );
    
    }

}

如果您的类无需bean,则可以声明bean如下:

MyClass clazz = mock(MyClass.class);

这些方法仍然保持不变:
当(clazz.Method())。然后()和void donothing()。当(clazz).method()。

You can use ,@MockBean annotation instead @SpyBean and can use Mockito.when().thenCallRealMethod() as you need to call real method and Mockito.when().thenReturn() at the same time if you want to mock some method. This way have to rid you of all your problems in that case and you can mock as you wanted.

For example :

@Component
class MyClass {

public String firstMethod(String value){
    return value;
}

public void secondMethod(String value){
    some code
}

@Component
class Main {
    @Autowire
    private MyClass clazz;
    
    public String getFirstMethod(String value){
        some code;
    }

    public void getSecondMethod(String value){
        clazz.secondMethod(value);
    }
}

In test class you have to declare a mock bean of your class as below and to mock them as you want. Instead thenReturn() you can use thenAnswer(), thenThrows(), then(), and instead doNothing() for void methods you can use doAnswer(), doThrow(), doReturn():

@MockBean(classes={MyClass.class})
public MainTest {

@Autowire
private Main main
@Autowire
privat MyClass clazz;

    @Test
    void getFirstMethod_mock(){
        String value = "something";
        when(clazz.firstMethod(anyString()).thenReturn("some string value");
        
        String actual = main.getFirstMethod(value );

        assertEquals("some string value", actual);
    
    }

    @Test
    void getFirstMethod_realMethod(){
        String value = "something";
        when(clazz.firstMethod(anyString()).thenCallRealMethod();
        
        String actual = main.getFirstMethod(value );

        assertEquals(realResultOfFirstMethod, actual);
    
    }

    @Test
    void getSecondMethod_mock(){
        String value = "something";
        doNothing().when(clazz).secondMethod(anyString());
        
        main.etSecondMethod(value );
    
    }

    @Test
    void getSecondMethod_realMethod(){
        String value = "something";
        doCallRealMethod().when(clazz).secondMethod(anyString());
        
        main.etSecondMethod(value );
    
    }

}

If your class not Bean you can declar it as below :

MyClass clazz = mock(MyClass.class);

The methods still stay the same :
when(clazz.method()).then() and for void doNothing().when(clazz).method().

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文