Como posso fazer um teste JUnit esperar?

93

Eu tenho um teste JUnit que quero esperar por um período de tempo, de forma síncrona. Meu teste JUnit se parece com isto:

@Test
public void testExipres(){
    SomeCacheObject sco = new SomeCacheObject();
    sco.putWithExipration("foo", 1000);
    //WAIT FOR 2 SECONDS
    assertNull(sco.getIfNotExipred("foo"));
}

Eu tentei Thread.currentThread (). Wait (), mas ele lança uma IllegalMonitorStateException (conforme o esperado). Existe algum truque para isso ou eu preciso de um monitor diferente?

Kylar
fonte

Respostas:

117

Que tal Thread.sleep(2000);? :)

Muel
fonte
14
Se você estiver usando ferramentas de análise de código como o sonarqube, eles reclamarão de Thread.sleepdizer algo como Usar Thread.sleep em um teste geralmente é uma má ideia. Ele cria testes frágeis que podem falhar de forma imprevisível, dependendo do ambiente ("Passa na minha máquina!") Ou da carga. Não confie no tempo (use mocks) ou use bibliotecas como Awaitility para testes assíncronos.
FuryFart
3
Essa resposta deve ser removida e considerada prejudicial. Uma resposta muito melhor abaixo é stackoverflow.com/a/35163873/1229735
yiati
71

Thread.sleep () pode funcionar na maioria dos casos, mas geralmente se você estiver esperando, está na verdade esperando que uma condição ou estado específico ocorra. Thread.sleep () não garante que tudo o que você está esperando realmente aconteceu.

Se você está esperando por uma solicitação de descanso, por exemplo, talvez ele retorne em 5 segundos, mas se você definir seu sono para 5 segundos no dia em que sua solicitação voltar em 10 segundos, seu teste irá falhar.

Para remediar isso, JayWay tem um ótimo utilitário chamado Awatility, que é perfeito para garantir que uma condição específica ocorra antes de você seguir em frente.

Tem uma boa API fluente também

await().until(() -> 
{
    return yourConditionIsMet();
});  

https://github.com/jayway/awaitility

Ben Glasser
fonte
Não consegui esperar para compilar no Android Studio.
IgorGanapolsky
Você adicionou esta linha ao arquivo gradle: compile 'org.awaitility: awaitility: 3.0.0'?
Samoht
não. você adicionaria isso em um caso de teste em que deseja esperar por uma condição específica
Ben Glasser
15

No caso do seu analisador de código estático (como SonarQube) reclamar, mas você não conseguir pensar em outra maneira, ao invés de dormir, você pode tentar um hack como: Awaitility.await().pollDelay(Durations.ONE_SECOND).until(() -> true); É conceitualmente incorreto, mas é o mesmo que Thread.sleep(1000).

A melhor maneira, claro, é passar um Callable, com sua condição apropriada, ao invés do trueque eu tenho.

https://github.com/awaitility/awaitility

rastaman
fonte
@Jitendra: Observe que o Awaitility costumava ter a Durationclasse, mas desde a versão 4 eles a renomearam para Durations.
Jacob van Lingen
Uma advertência: se você quiser que o atraso da pesquisa seja de 10 segundos ou mais, certifique-se de aumentar o tempo limite para mais de 10 segundos, pois esse é o tempo limite padrão e a espera reclamará que o tempo limite é menor que o atraso da pesquisa.
foco em
12

Você pode usar a biblioteca java.util.concurrent.TimeUnit que usa Thread.sleep internamente. A sintaxe deve ser semelhante a esta:

@Test
public void testExipres(){
    SomeCacheObject sco = new SomeCacheObject();
    sco.putWithExipration("foo", 1000);

    TimeUnit.MINUTES.sleep(2);

    assertNull(sco.getIfNotExipred("foo"));
}

Esta biblioteca fornece uma interpretação mais clara para a unidade de tempo. Você pode usar 'HOURS' / 'MINUTES' / 'SECONDS'.

Akshay Pagar
fonte
Se eu iniciar um thread de trabalho de dentro de um teste, isso sleep()afetará o thread de trabalho?
Anthony Kong
6

Você também pode usar o CountDownLatch objeto conforme explicado aqui .

Pierre Voisin
fonte
0

Há um problema geral: é difícil zombar do tempo. Além disso, é uma prática muito ruim colocar código de longa execução / espera em um teste de unidade.

Portanto, para tornar uma API de agendamento testável, usei uma interface com uma implementação real e uma simulação como esta:

public interface Clock {

    public long getCurrentMillis();

    public void sleep(long millis) throws InterruptedException;

}

public static class SystemClock implements Clock {

    @Override
    public long getCurrentMillis() {
        return System.currentTimeMillis();
    }

    @Override
    public void sleep(long millis) throws InterruptedException {
        Thread.sleep(millis);
    }

}

public static class MockClock implements Clock {

    private final AtomicLong currentTime = new AtomicLong(0);


    public MockClock() {
        this(System.currentTimeMillis());
    }

    public MockClock(long currentTime) {
        this.currentTime.set(currentTime);
    }


    @Override
    public long getCurrentMillis() {
        return currentTime.addAndGet(5);
    }

    @Override
    public void sleep(long millis) {
        currentTime.addAndGet(millis);
    }

}

Com isso, você pode imitar o tempo em seu teste:

@Test
public void testExipres() {
    MockClock clock = new MockClock();
    SomeCacheObject sco = new SomeCacheObject();
    sco.putWithExipration("foo", 1000);
    clock.sleep(2000) // WAIT FOR 2 SECONDS
    assertNull(sco.getIfNotExpired("foo"));
}

Um mock avançado multi-threading para Clocké muito mais complexo, é claro, mas você pode fazer isso com ThreadLocalreferências e uma boa estratégia de sincronização de tempo, por exemplo.

Dávid Horváth
fonte