如何使用@Qualifier 模拟一个bean

How to mock a bean with @Qualifier

我有一个 class,它有一个带有 @Qualifier 的 Bean(参见 AerospikeClient)。在编写测试用例时,我无法使用 @MockBean 模拟 bean。我总是在 verify(aerospikeClient).put

中得到 aerospikeClient 的空指针异常

我的class

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

    

}

我的测试class

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 class 我应该像上面那样直接自动装配它还是像这样创建一个 bean?

@TestConfiguration
    static class AerospikeServiceImplTestContextConfiguration {

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

我对 Spring Boot 比较陌生,因此非常感谢任何最佳实践建议。

我的期末考试class。有用。不需要@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());

    }

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

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