Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I test code that uses ExecutorService without using timeout in Mockito?

I have a class.

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

How can I verify with Mockito that strategyByType.get(feedType).feed(feedDate, uuid) was called when I call feed method?

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

This is my version. But I don't want to use Mockito timeout method. It's in my opinion not a good solution. Help me please!

like image 896
Yakov Burtsev Avatar asked Jan 04 '23 08:01

Yakov Burtsev


2 Answers

When I test code that are dependent on executors I usually try to use an executor implementation that runs the task immediately in the same thread as submitted in order to remove all the hassle of multithreading. Perhaps you can add another constructor for your FeedServiceImpl class that lets you supply your own executor? The google guava lib has a MoreExecutors.directExecutor() method that will return such an executor. That would mean your test setup would change to something like:

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

The rest of the test code should then work as it is, and you can safely drop the timeout verification mode parameter.

like image 108
Per Huss Avatar answered Jan 06 '23 01:01

Per Huss


A little improvement for @Per Huss answer so you won't have to change your constructor you can use ReflectionTestUtils class which is part of the spring-test package.

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

Now in the tests your executorService will implement the MoreExecutors.newDirectExecutorService() and not Executors.newSingleThreadExecutor().

like image 27
Ohad Abraham Avatar answered Jan 06 '23 01:01

Ohad Abraham