【问题标题】:How to mock a bean with @Qualifier如何使用@Qualifier 模拟 bean
【发布时间】:2022-02-07 11:54:34
【问题描述】:

我有一个类,它有一个带有 @Qualifier 的 Bean(参见 AerospikeClient)。在编写测试用例时,我无法使用 @MockBean 模拟 bean。我总是在 verify(aerospikeClient).put 中得到 aerospikeClient 的空指针异常

我的班级

package com.a.recharge.service.impl;

import com.aerospike.client.*;
import com.aerospike.client.policy.WritePolicy;
import com.a.recharge.entity.aero.PacksInfo;
import com.a.recharge.service.AerospikeService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class AerospikeServiceImpl implements AerospikeService {

    @Autowired
    @Qualifier("oldAerospikeClient")
    private AerospikeClient aerospikeClient;

    @Value("${config.aerospike.defaultNS:test}")
    private String nameSpace;

    @Value("${config.aerospike.set.txn:''}")
    private String setname;

    @Value("${config.aerospike.ttl:1800}")
    private int ttl;

    @Value("${config.aerospike.defaultNS}")
    public String defaultNS;

    @Value("${config.aerospike.host}")
    private String aerospikeHost;

    @Value("${config.aerospike.port}")
    private String aerospikePort;

    @Value("${config.aerospike.userName}")
    public String aerospikeUserName;

    @Value("${config.aerospike.password}")
    public String aerospikePassword;

    @Value("${config.aerospike.expiration}")
    public long expiration;

    @Value("${config.aerospike.expiration.no}")
    public long noExpiration;


    @Override
    public void insertRecord(String keyString, PacksInfo packsInfo) {
        // log.info("Saving Records in Aerospike, " + keyString+ ", "+ packsInfo);
        WritePolicy writePolicy = new WritePolicy();
        writePolicy.expiration = ttl; // seconds
        writePolicy.sendKey = true;

        // columns
        Bin[] bins = new Bin[3];

        bins[0] = new Bin("type", packsInfo.getClass().getName());
        bins[2] = new Bin("@user_key", keyString);
        bins[1] = new Bin("value", packsInfo);

        try {
            Key key = new Key("test", "PacksInfo", keyString);
            aerospikeClient.put(writePolicy, key, bins);
        } catch (AerospikeException e) {
            log.error("Exception raised. Root Cause {}, Message {}", ExceptionUtils.getRootCause(e),
                      ExceptionUtils.getMessage(e));
        }
    }

    

}

我的测试课

package com.a.recharge.service;

import com.aerospike.client.AerospikeClient;
import com.aerospike.client.Bin;
import com.aerospike.client.Host;
import com.aerospike.client.Key;
import com.aerospike.client.policy.ClientPolicy;
import com.aerospike.client.policy.WritePolicy;
import com.a.recharge.config.AerospikeConfiguration;
import com.a.recharge.service.impl.AerospikeServiceImpl;
import com.a.recharge.util.CollectionUtil;
import com.a.recharge.util.TestUtil;
import org.apache.commons.lang3.StringUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.*;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.junit4.SpringRunner;
import org.testng.annotations.BeforeMethod;

import java.util.List;
import java.util.stream.Collectors;

import static org.junit.Assert.assertEquals;

@RunWith(MockitoJUnitRunner.class)
// @SpringBootTest(classes = AerospikeServiceImpl.class)

public class AerospikeServiceImplTest {

    @Mock(name ="oldAerospikeClient")
    private AerospikeClient aerospikeClient;// = TestUtil.getAerospikeClient();

    @Captor
    private ArgumentCaptor<WritePolicy> captor1;

    @Captor
    private ArgumentCaptor<Key> captor2;

    @Captor
    private ArgumentCaptor<Bin[]> captor3;

    @InjectMocks
//    @Autowired
    AerospikeServiceImpl aerospikeService;

//    @BeforeMethod(alwaysRun = true)
//    public void setUp() {
//        MockitoAnnotations.initMocks(this);
//    }


    @Test
    public void checkInsert(){
        Mockito.doNothing().when(aerospikeClient).put(Mockito.any(), Mockito.any(), Mockito.any());
        Mockito.verify(aerospikeClient).put(captor1.capture(), captor2.capture(), captor3.capture());
        aerospikeService.insertRecord(TestUtil.keyString, TestUtil.getPacksInfo());

        assertEquals(1800, captor1.getValue().expiration);
        assertEquals(true, captor1.getValue().expiration);
    }
}

我改为 InjectMocks 并使用了 Mockito.doNothing(),但仍然是同样的错误。 是不是因为 aerospikeClient 的 put 方法是 final 的?

另一个问题是,为了测试 AerospikeServiceImpl 类,我应该像上面那样直接自动装配它还是像这样创建一个 bean?

@TestConfiguration
    static class AerospikeServiceImplTestContextConfiguration {

        @Bean
        public AerospikeServiceImpl AerospikeServiceImpl() {
            return new AerospikeServiceImpl();
        }
    }

我对 Spring Boot 比较陌生,因此任何最佳实践的建议都将受到高度赞赏。

我的期末考试课。有用。不需要@TestConfiguration。

@RunWith(MockitoJUnitRunner.class)
public class AerospikeServiceImplTest {

    @Mock //(name ="oldAerospikeClient")
    private AerospikeClient aerospikeClient;

    @Captor
    private ArgumentCaptor<Key> captor2;

    @InjectMocks
//    @Autowired
    private AerospikeServiceImpl aerospikeService;

    @Test
    public void checkInsert(){
        aerospikeService.insertRecord(TestUtil.keyString, TestUtil.getPacksInfo());

        Mockito.verify(aerospikeClient).put(captor1.capture(), captor2.capture(), captor3.capture());
        assertEquals("test", captor2.getValue().namespace);
        assertEquals("PacksInfo", captor2.getValue().setName);
        assertEquals("MOBILE_JIOPREPAID_GJ", captor2.getValue().userKey.toString());

    }

【问题讨论】:

  • 根据 doco (docs.spring.io/spring-boot/docs/current/api/org/springframework/…) 支持带限定符的 MockBean。
  • 你想做什么?单元测试还是集成测试?您正在混合两者(MockitoJUnitRunner 和 SpringBootTest)。您还对@InjectMocks 发表了评论。请查看这样的教程:springboottutorial.com/… 并从那里开始。
  • 我正在尝试编写单元测试,因为我遇到了空指针错误,所以我尝试使用 SpringBootTest。我也尝试过 InjectMocks。还是同样的错误。你能在本地运行它并检查吗?
  • @Qualifier 与您当前的设置无关,您只使用 Mockito。您的测试有问题的另一件事是您的verify 调用应该在insertRecord 调用之后而不是之前。最后你得到一个错误/堆栈跟踪,请将它包含在你的问题中并指向实际生成 NullPointerException 的行。
  • java.lang.NullPointerException at com.a.recharge.service.AerospikeServiceImplTest.checkInsert(AerospikeServiceImplTest.java:76) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl .invoke(NativeMethodAccessorImpl.java:62) 在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 在 java.lang.reflect.Method.invoke(Method.java:498) 在 org.junit.runners.model。 FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)

标签: spring spring-boot unit-testing junit mockito


【解决方案1】:

aerospikeClient.put() 是最后一个方法。 Mockito 2 现在支持模拟 final 方法,但是这个特性必须被显式激活;它可以通过 mockito 扩展机制来完成,方法是创建包含一行的文件 src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker:

mock-maker-inline 请参考此链接:https://github.com/mockito/mockito/wiki/What's-new-in-Mockito-2#mock-the-unmockable-opt-in-mocking-of-final-classesmethods

【讨论】:

  • 我用这一行添加了这个文件 test/resources/mockito-extensions:mock-maker-inline。有用。谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-21
  • 2018-08-14
  • 2019-06-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多