首页 > 解决方案 > 如何模拟具有反应核心单声道代码的类

问题描述

我有以下休息控制器,我正在尝试使用 Mockito 进行测试。

@RestController
public class BackupQueueController {

    @Autowired
    JmsTemplate jmsTemplate;

    @Autowired
    SettingsService settingsService;

    @Resource
     Environment env;

    private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(BackupQueueController.class);

    @GetMapping("/eib3/startBackupQueueDraining")
    public String startBackupQueueDraining(){
        String response= IcisApiServiceConstant.EMPTY;
        final String backupQueueEndpoint = env.getProperty("record_order_backup_queue");
        final String origQueueEndPoint=env.getProperty("record_order_queue");
        final String logPrefix=System.currentTimeMillis()+"-"+" :: BackupQueueController :: startBackupQueueDrain :: " ;
        final int totalMessages=getNumberOfMessageInQueue(backupQueueEndpoint);
        LOGGER.info(logPrefix+" started .");
        Mono.fromRunnable( () -> {
           //this block of code will be executed in separate thread!
              try {
                  //get total count of messages in backup queue.
                  for(int i=0;i<totalMessages;i++) {
                      //get message from backup queue.
                      if(StringUtils.equalsIgnoreCase(settingsService.getSettingsProperties().getAllowDrain(),IcisApiServiceConstant.TRUE))
                      {
                          String msgPayload = (String) jmsTemplate.receiveAndConvert(backupQueueEndpoint);
                          AddSalesOrderDocument addSalesOrderDocument = AddSalesOrderDocument.Factory.parse(msgPayload);
                          String orderId = addSalesOrderDocument.getAddSalesOrder().getDataArea().getSalesOrder().getHeader().getDocumentIds().getTransactionId().getId();
                          LOGGER.info(logPrefix + " order Id ::" + orderId + " :: request payload :: \n" + msgPayload);
                          // put message onto original queue.
                          jmsTemplate.convertAndSend(origQueueEndPoint,msgPayload);
                          //now  thread.sleep
                          Thread.sleep(Long.parseLong(settingsService.getSettingsProperties().getDrainRate()));
                      }else{
                          LOGGER.info(logPrefix+" Allow Drain :: "+settingsService.getSettingsProperties().getAllowDrain() +" :: exiting from backup queue draining process ");
                          break;
                      }
                  }
              }catch (Exception e){
                  LOGGER.error(logPrefix,e.getMessage());
              }

        }).subscribeOn(Schedulers.newElastic("backupQueue"))
        .subscribe();
        response+=" draining process has started to drain "+totalMessages+" orders.";
        return  response;
    }


    public int getNumberOfMessageInQueue(String queueName) {

        Mono<Integer> messageCount= Mono.fromCallable( () -> {
             return jmsTemplate.browse(queueName, (session, queueBrowser) ->
                    Collections.list(queueBrowser.getEnumeration()).size()
            );
        }).timeout(Duration.ofSeconds(5L)).subscribeOn(Schedulers.elastic());

        return  messageCount.block();   // Getting null pointer exception while testing //with junit
    }

}

控制器使用以下类

@Service
@RefreshScope
public class SettingsService {

    @Autowired
    private SettingsProperties properties;

    public SettingsProperties getSettingsProperties() {
        return this.properties;
    }
}

@ConfigurationProperties(prefix = "app")
@Component
public class SettingsProperties {

    private String name;
    private String recordOrderQueue;
    private String drainRate;
    private String allowDrain;
    private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(SettingsProperties.class);

    public String getDrainRate() {
        return drainRate;
    }

    public void setDrainRate(String drainRate) {
        LOGGER.info(getLogPrefix()+ "::  setDrainRate :: drain rate"+drainRate);
        this.drainRate = drainRate;
    }

    public String getRecordOrderQueue() {
        return recordOrderQueue;
    }

    public void setRecordOrderQueue(String recordOrderQueue) {
        LOGGER.info(getLogPrefix()+ "::  setRecordOrderQueue :: recordOrderQueue"+recordOrderQueue);
        this.recordOrderQueue = recordOrderQueue;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAllowDrain() {
        return allowDrain;
    }

    public void setAllowDrain(String allowDrain) {
        LOGGER.info(getLogPrefix()+ "::  setAllowDrain :: allowDrain"+allowDrain);
        this.allowDrain = allowDrain;
    }

    public static String getLogPrefix(){
         return  System.currentTimeMillis()+" SettingsProperties :: ";
    }

    @PostConstruct
    public void postConstruct() {
        LOGGER.info("postConstruct: {}@{} {}",
                this.getClass().getSimpleName(), System.identityHashCode(this), this);
    }
}

我写了以下测试用例

@RunWith(SpringRunner.class)
public class BackupQueueControllerTest {

    @InjectMocks
    BackupQueueController backupQueueController= new BackupQueueController();

    @Mock
    JmsTemplate jmsTemplate;

    @Mock
    SettingsService settingsService;

    @Mock
    Environment env;

    private  static final String ORDER ="<document>  <orderId> 1231</orderId></document>";
     @Test
    public void startBackupQueueDrainingTest(){

        when(env.getProperty("record_order_backup_queue")).thenReturn("fake_backup_queue");
        when(backupQueueController.getNumberOfMessageInQueue("fake_backup_queue")).thenReturn(10);
        when(jmsTemplate.receiveAndConvert("fake_backup_queue")).thenReturn(ORDER);
        when(settingsService.getSettingsProperties().getAllowDrain()).thenReturn("yes");
        when(settingsService.getSettingsProperties().getDrainRate()).thenReturn("2000");
        doNothing().when(jmsTemplate).convertAndSend(anyString(),anyString());
        assertEquals(backupQueueController.startBackupQueueDraining(),"draining process has started to drain "+10+" orders.");
     }
}

上面的测试用例在 messageCount.block() 处抛出空指针异常

现在我有以下问题。

  1. 如何模拟 getNumberOfMessageInQueue() 方法?

  2. 如何模拟 settingsService.getSettingsProperties().getAllowDrain() ?

标签: javajunitmockito

解决方案


回答你的第一个问题:你不能模拟你实际测试的类的方法。如果你想模拟它,你应该将它提取到一个自己的类中,并在你的测试期间注入这个类的模拟,就像你已经做的那样SettingsService

当前测试中的 NullPointerException 来自 not mocking jmsTemplate.browse(queueName, (session, queueBrowser)。您应该模拟您所依赖的 bean 的每个调用,否则,它们的方法调用将返回null,因为您没有指定它们的行为。

回答你的第二个问题:你首先必须模拟结果settingService.getSettingsProperties()并返回一个SettingsProperties模拟然后模拟这个对象settingsProperties.getAllowDrain(),例如:

@Mock 
private SettingsService settingsService;

@Mock
private SettingsProperties settingsProperties;

@Test
public void test() {
  when(settingsService.getSettingsProperties()).thenReturn(settingsProperties);
  when(settingsProperties.getAllowDrain()).thenReturn("FOO");
}

如果你模拟你的类的依赖,永远不要忘记模拟你在业务逻辑中访问的所有方法,否则 Mockito 会返回null


推荐阅读