Mockito: simulação de inicialização de campo privado

94

Como posso simular uma variável de campo que está sendo inicializada inline?

class Test {
    private Person person = new Person();
    ...
    public void testMethod() {
        person.someMethod();
        ...
    }
}

Aqui, eu quero simular person.someMethod()enquanto testo o Test.testMethod()método para o qual preciso simular a inicialização da personvariável. Qualquer pista?

Editar: não tenho permissão para modificar a classe Person.

Uma corrida
fonte
1
Este link pode ser útil para você stackoverflow.com/questions/13645571/…
Popeye
2
Você deve refatorar seu código para que possa passar em uma simulação para Person. As opções incluem adicionar um construtor para fazer isso ou adicionar um método setter.
Tim Biegeleisen

Respostas:

108

O Mockito vem com uma classe auxiliar para economizar um pouco de reflexão sobre o código da placa padrão:

import org.mockito.internal.util.reflection.Whitebox;

//...

@Mock
private Person mockedPerson;
private Test underTest;

// ...

@Test
public void testMethod() {
    Whitebox.setInternalState(underTest, "person", mockedPerson);
    // ...
}

Atualização: Infelizmente, a equipe do mockito decidiu remover a classe no Mockito 2. Então você está de volta a escrever seu próprio código padrão de reflexão, usar outra biblioteca (por exemplo, Apache Commons Lang ) ou simplesmente roubar a classe Whiteboxlicenciada pelo MIT ).

Atualização 2: JUnit 5 vem com suas próprias classes ReflectionSupport e AnnotationSupport que podem ser úteis e evitar que você obtenha outra biblioteca.

Ralf
fonte
Estou espionando meu objeto alvo por outros motivos e, neste caso, quando meu objeto é espião, não posso definir o estado interno dessa forma.
Arun
Por que não? Estou usando com espiões. Crie uma instância de Person. Faça o stub do que precisar de stub e defina-o na instância de teste.
Ralf
Aviso: Whitebox está no internalpacote e parece não funcionar mais no Mockito 2.6.2.
Nova de
Você pode usar FieldSetter.setField (). Eu dei um exemplo abaixo para o mesmo.
Raj Kumar de
1
@Ralf Porque alterar um nome de referência em Java sempre deve resultar em um erro de compilação.
Joe Coder
67

Bem tarde para a festa, mas fui atingido aqui e recebi a ajuda de um amigo. O problema era não usar PowerMock. Isso funciona com a versão mais recente do Mockito.

Mockito vem com isso org.mockito.internal.util.reflection.FieldSetter.

O que ele basicamente faz é ajudá-lo a modificar campos privados usando reflexão.

É assim que você o usa:

@Mock
private Person mockedPerson;
private Test underTest;

// ...

@Test
public void testMethod() {
    FieldSetter.setField(underTest, underTest.getClass().getDeclaredField("person"), mockedPerson);
    // ...
    verify(mockedPerson).someMethod();
}

Dessa forma, você pode passar um objeto simulado e verificá-lo posteriormente.

Aqui está a referência.

Raj Kumar
fonte
FieldSetternão está mais disponível em Mockito 2.x.
Ralf de
4
@Ralf Estou usando a versão mockito-core-2.15.0. Está disponível lá. static.javadoc.io/org.mockito/mockito-core/2.0.15-beta/org/… . Ainda beta.
Raj Kumar de
1
@RajKumar Class#getDeclaredFieldaceita um único parâmetro, portanto, os parênteses precisam ter a seguinte aparência FieldSetter.setField(underTest, underTest.getClass().getDeclaredField("person"), mockedPerson);. Foi assim que entendi errado e pensei que seria uma boa ideia consertar no seu exemplo. Obrigado por responder.
Dapeng Li
1
usar API interna não é a melhor ideia
David
@RajKumar Legal, mas como Zimbo Rodger mencionou: É uma boa ideia usar uma API interna? Por que é interno de forma aguda? É muito útil para teste.
Willi Mentzel
31

Caso você use Spring Test, experimente org.springframework.test.util.ReflectionTestUtils

 ReflectionTestUtils.setField(testObject, "person", mockedPerson);
David
fonte
1
Ótimo! Pode ajudar a criar um link para este artigo e talvez incluir as dependências necessárias dele: baeldung.com/spring-reflection-test-utils
Willi Mentzel
build.gradle.kts:testImplementation("org.springframework:spring-test:5.1.2.RELEASE")
Willi Mentzel
28

Já encontrei a solução para este problema que esqueci de postar aqui.

@RunWith(PowerMockRunner.class)
@PrepareForTest({ Test.class })
public class SampleTest {

@Mock
Person person;

@Test
public void testPrintName() throws Exception {
    PowerMockito.whenNew(Person.class).withNoArguments().thenReturn(person);
    Test test= new Test();
    test.testMethod();
    }
}

Os pontos principais para esta solução são:

  1. Executando meus casos de teste com PowerMockRunner: @RunWith(PowerMockRunner.class)

  2. Instrua Powermock para se preparar Test.classpara a manipulação de campos privados:@PrepareForTest({ Test.class })

  3. E, finalmente, simule o construtor da classe Person:

    PowerMockito.mockStatic(Person.class); PowerMockito.whenNew(Person.class).withNoArguments().thenReturn(person);

Uma corrida
fonte
7
Sua explicação fala sobre a mockStaticfunção, mas isso não é representado em seu exemplo de código. O exemplo de código deve ter a mockStaticchamada ou isso não é necessário para os construtores?
Shadoninja
10

O código a seguir pode ser usado para inicializar o mapeador na simulação do cliente REST. O mappercampo é privado e precisa ser definido durante a configuração do teste de unidade.

import org.mockito.internal.util.reflection.FieldSetter;

new FieldSetter(client, Client.class.getDeclaredField("mapper")).set(new Mapper());
Jarda Pavlíček
fonte
5

Usando o guia de @ Jarda, você pode definir isso se precisar definir a variável com o mesmo valor para todos os testes:

@Before
public void setClientMapper() throws NoSuchFieldException, SecurityException{
    FieldSetter.setField(client, client.getClass().getDeclaredField("mapper"), new Mapper());
}

Mas tome cuidado, pois a definição de valores privados diferentes deve ser tratada com cuidado. Se eles forem privados, por algum motivo.

Exemplo, eu uso, por exemplo, para alterar o tempo de espera de um sono nos testes de unidade. Em exemplos reais, quero dormir por 10 segundos, mas no teste de unidade fico satisfeito se for imediato. Em testes de integração, você deve testar o valor real.

Hugo Dias
fonte