【问题标题】:How do I test code that uses ExecutorService without using timeout in Mockito?如何在 Mockito 中测试使用 ExecutorService 而不使用超时的代码?
【发布时间】:2017-10-12 14:25:57
【问题描述】:

我有课。

public class FeedServiceImpl implements FeedService {
    private final Map<FeedType, FeedStrategy> strategyByType;
    private final ExecutorService executorService = Executors.newSingleThreadExecutor(); 

    public FeedServiceImpl(Map<FeedType, FeedStrategy> strategyByType) {
        if (strategyByType.isEmpty()) throw new IllegalArgumentException("strategyByType map is empty");
        this.strategyByType = strategyByType;
    }

    @Override
    public void feed(LocalDate feedDate, FeedType feedType, String uuid) {
        if (!strategyByType.containsKey(feedType)) {
            throw new IllegalArgumentException("Not supported feedType: " + feedType);
        }
        executorService.submit(() -> runFeed(feedType, feedDate, uuid));
    }


    private FeedTaskResult runFeed(FeedType feedType, LocalDate feedDate, String uuid) {
        return strategyByType.get(feedType).feed(feedDate, uuid);
    }
}

如何使用 Mockito 验证在我调用 feed 方法时调用了 strategyByType.get(feedType).feed(feedDate, uuid)

@RunWith(MockitoJUnitRunner.class)
public class FeedServiceImplTest {
    private LocalDate date = new LocalDate();
    private String uuid = "UUID";

    private FeedService service;
    private Map<FeedType, FeedStrategy> strategyByType;

    @Before
    public void setUp() {
        strategyByType = strategyByTypeFrom(TRADEHUB);
        service = new FeedServiceImpl(strategyByType);
    }

    private Map<FeedType, FeedStrategy> strategyByTypeFrom(FeedSource feedSource) {
        return bySource(feedSource).stream().collect(toMap(identity(), feedType -> mock(FeedStrategy.class)));
    }

    @Test
    public void feedTest() {
        service.feed(date, TH_CREDIT, uuid);
        verify(strategyByType.get(TH_CREDIT), timeout(100)).feed(date, uuid);
    }
}

这是我的版本。但我不想使用 Mockito 超时方法。在我看来,这不是一个好的解决方案。请帮帮我!

【问题讨论】:

    标签: java unit-testing junit mockito


    【解决方案1】:

    当我测试依赖于 executor 的代码时,我通常会尝试使用 executor 实现,该实现会立即在提交的同一线程中运行任务,以消除多线程的所有麻烦。也许您可以为您的FeedServiceImpl 类添加另一个构造函数,让您提供自己的执行程序? google guava lib 有一个MoreExecutors.directExecutor() 方法,它将返回这样一个执行程序。这意味着您的测试设置将更改为:

    service = new FeedServiceImpl(strategyByType, MoreExecutors.directExecutor());
    

    然后测试代码的其余部分应该可以正常工作,您可以放心地删除超时验证模式参数。

    【讨论】:

      【解决方案2】:

      @Per Huss 回答的一点改进,因此您不必更改构造函数,您可以使用 ReflectionTestUtils 类,它是 spring-test 包的一部分。

      @Before
      public void setUp() {
          strategyByType = strategyByTypeFrom(TRADEHUB);
          service = new FeedServiceImpl(strategyByType);
          ReflectionTestUtils.setField(service , "executorService", MoreExecutors.newDirectExecutorService());
      }
      

      现在在测试中,您的 executorService 将实现 MoreExecutors.newDirectExecutorService() 而不是 Executors.newSingleThreadExecutor()。

      【讨论】:

      • 虽然它是现场注入一个实例(这可能很可怕并导致一些代码没有被测试),但这是最好的解决方案,除非依赖注入执行器。谢谢!
      【解决方案3】:

      如果你想运行一个 real FeedStrategy(而不是一个模拟的,正如另一个答案中所建议的那样),那么你需要一种方法让 FeedStrategy 以某种方式 指示测试线程何时完成。例如,通过使用CountDownLatch 或设置您的测试可以等待的标志。

      public class FeedStrategy {
      
          private boolean finished;
      
          public void feed(...) {
              ...
              this.finished = true;
          }
      
          public boolean isFinished() {
              return this.finished;
          }
      }
      

      或者FeedStrategy 有一些副作用,您可以等待?

      如果以上任何一项都成立,那么您可以使用Awaitility 来实现服务员。例如:

          @Test
          public void aTest() {
              // run your test
      
              // wait for completion
              await().atMost(100, MILLISECONDS).until(feedStrategyHasCompleted());
      
              // assert 
              // ...
          }
      
          private Callable<Boolean> feedStrategyHasCompleted() {
              return new Callable<Boolean>() {
                  public Boolean call() throws Exception {
                      // return true if your condition has been met
                      return ...;
                  }
              };
          }
      

      【讨论】:

        【解决方案4】:

        您需要为 FeedStrategy.class 制作一个对您的测试可见的模拟,以便您能够验证是否调用了 FeedStrategy.feed 方法:

        @RunWith(MockitoJUnitRunner.class)
        public class FeedServiceImplTest {
        private LocalDate date = new LocalDate();
        private String uuid = "UUID";
        
        private FeedService service;
        private Map<FeedType, FeedStrategy> strategyByType;
        private FeedStrategy feedStrategyMock;
        
        
        @Before
        public void setUp() {
            strategyByType = strategyByTypeFrom(TRADEHUB);
            service = new FeedServiceImpl(strategyByType);
            feedStrategyMock = Mockito.mock();
        }
        
        private Map<FeedType, FeedStrategy> strategyByTypeFrom(FeedSource feedSource) {
            return bySource(feedSource).stream().collect(toMap(identity(), feedType -> feedStrategyMock));
        }
        
        @Test
        public void feedTest() {
            service.feed(date, TH_CREDIT, uuid);
            verify(feedStrategyMock).feed(date, uuid);
        }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-02-03
          • 2019-05-24
          • 1970-01-01
          • 2013-04-18
          • 1970-01-01
          • 1970-01-01
          • 2021-05-17
          相关资源
          最近更新 更多