Mockito: Injete objetos reais em campos privados @Autowired

190

Estou usando o Mockito @Mocke as @InjectMocksanotações para injetar dependências em campos particulares anotados no Spring @Autowired:

@RunWith(MockitoJUnitRunner.class)
public class DemoTest {
    @Mock
    private SomeService service;

    @InjectMocks
    private Demo demo;

    /* ... */
}

e

public class Demo {

    @Autowired
    private SomeService service;

    /* ... */
}

Agora eu também gostaria de injetar objetos reais em @Autowiredcampos privados (sem setters). Isso é possível ou o mecanismo está limitado apenas à injeção de Mocks?

user2286693
fonte
5
Normalmente, quando você zomba das coisas, isso implica que você não se importa muito com o objeto concreto; que você realmente se importa apenas com o comportamento do objeto zombado. Talvez você queira fazer um teste de integração? Ou, você poderia fornecer uma justificativa para o motivo pelo qual deseja objetos zombados e concretos vivendo juntos?
Makoto
2
Bem, estou lidando com código legado e levaria muito tempo quando (...). ThenReturn (...) para configurar o mock apenas para evitar alguns NPEs e similares. Por outro lado, um objeto real pode ser usado com segurança para isso. Portanto, seria muito útil ter uma opção para injetar objetos reais junto com zombarias. Mesmo que seja um cheiro de código, considero razoável neste caso particular.
user2286693
Não esqueça MockitoAnnotations.initMocks(this);o @Beforemétodo. Eu sei que não está diretamente relacionado à pergunta original, mas a qualquer um que aparecer mais tarde, isso precisará ser adicionado para tornar isso rodável.
Cuga
7
@Cuga: se você usar o corredor Mockito para JUnit, ( @RunWith(MockitoJUnitRunner.class)), você não precisa da linhaMockitoAnnotations.initMocks(this);
Clint Eastwood
1
Thanks-- Eu nunca soube que e sempre foram especificando
Cuga

Respostas:

304

Usar @Spyanotação

@RunWith(MockitoJUnitRunner.class)
public class DemoTest {
    @Spy
    private SomeService service = new RealServiceImpl();

    @InjectMocks
    private Demo demo;

    /* ... */
}

O Mockito considerará todos os campos que possuem @Mockou @Spyanotação como candidatos potenciais a serem injetados na instância anotada com @InjectMocksanotação. No caso acima, a 'RealServiceImpl'instância será injetada na 'demo'

Para mais detalhes consulte

Mockito-home

@Espião

@Zombar

Dev Blanked
fonte
9
+1: funcionou para mim ... exceto para objetos String. Mockito reclama:Mockito cannot mock/spy following: - final classes - anonymous classes - primitive types
Adrian Pronk
Graças ele trabalhou para mim também :) Para fazer uso falsa procuração outra para uso implementação Real Spy
Swarit Agarwal
Obrigado! Isso é exatamente o que eu precisava!
Nterry
2
No meu caso, Mockito não está injetando um Spy. No entanto, ele injeta uma zombaria. O campo é privado e sem um levantador.
Vituel 03/05
8
BTW, não há necessidade new RealServiceImpl(), @Spy private SomeService service;cria um objeto real usando o construtor padrão antes de espioná-lo de qualquer maneira.
parxier
20

Além da resposta @Dev Blanked, se você quiser usar um bean existente criado pelo Spring, o código poderá ser modificado para:

@RunWith(MockitoJUnitRunner.class)
public class DemoTest {

    @Inject
    private ApplicationContext ctx;

    @Spy
    private SomeService service;

    @InjectMocks
    private Demo demo;

    @Before
    public void setUp(){
        service = ctx.getBean(SomeService.class);
    }

    /* ... */
}

Dessa forma, você não precisa alterar seu código (adicione outro construtor) apenas para fazer os testes funcionarem.

Yoaz Menda
fonte
2
@Aada Você pode elaborar?
Yoaz Menda
1
Qual biblioteca vem isso, só posso ver InjectMocks em org.mockito
Sameer
1
@sameer import org.mockito.InjectMocks;
Yoaz Menda 26/06/19
Adicionando um comentário para cancelar o Aada. Isso funcionou para mim. Eu não poderia "simplesmente não usar a injeção de campo", como sugerido por outras respostas, por isso tive que garantir que os Autowiredcampos das dependências fossem criados corretamente.
Scrambo 29/10/19
1
Eu tentei isso. mas estou recebendo exceção de ponteiro nulo do método de instalação
Akhil Surapuram
3

Mockito não é uma estrutura DI e até mesmo estruturas DI incentivam injeções de construtores sobre injeções de campo.
Então você apenas declara um construtor para definir dependências da classe em teste:

@Mock
private SomeService serviceMock;

private Demo demo;

/* ... */
@BeforeEach
public void beforeEach(){
   demo = new Demo(serviceMock);
}

Usar o Mockito spypara o caso geral é um péssimo conselho. Isso torna a classe de teste quebradiça, não direta e propensa a erros: o que é realmente ridicularizado? O que é realmente testado?
@InjectMockse @Spytambém prejudica o design geral, pois incentiva classes inchadas e responsabilidades mistas nas classes.
Por favor, leia o spy()javadoc antes de usá-lo cegamente (a ênfase não é minha):

Cria um espião do objeto real. O espião chama métodos reais , a menos que sejam roubados. Espiões de verdade devem ser usados ​​com cuidado e ocasionalmente , por exemplo, ao lidar com código legado.

Como de costume, você lerá partial mock warning: A programação orientada a objetos aborda a complexidade, dividindo a complexidade em objetos SRPy específicos e separados. Como o mock parcial se encaixa nesse paradigma? Bem, simplesmente não ... Simulação parcial geralmente significa que a complexidade foi movida para um método diferente no mesmo objeto. Na maioria dos casos, não é dessa maneira que você deseja projetar seu aplicativo.

No entanto, existem casos raros em que zombarias parciais são úteis: lidar com código que você não pode alterar facilmente (interfaces de terceiros, refatoração provisória de código herdado etc.) No entanto, eu não usaria zombarias parciais para novos, orientados a testes e bem- código projetado.

davidxxx
fonte
0

Na primavera, há um utilitário dedicado chamado ReflectionTestUtilspara esse fim. Pegue a instância específica e injete no campo.


@Spy
..
@Mock
..

@InjectMock
Foo foo;

@BeforeEach
void _before(){
   ReflectionTestUtils.setField(foo,"bar", new BarImpl());// `bar` is private field
}
takacsot
fonte
-1

Sei que essa é uma pergunta antiga, mas fomos confrontados com o mesmo problema ao tentar injetar Strings. Por isso, inventamos uma extensão JUnit5 / Mockito que faz exatamente o que você deseja: https://github.com/exabrial/mockito-object-injection

EDITAR:

@InjectionMap
 private Map<String, Object> injectionMap = new HashMap<>();

 @BeforeEach
 public void beforeEach() throws Exception {
  injectionMap.put("securityEnabled", Boolean.TRUE);
 }

 @AfterEach
 public void afterEach() throws Exception {
  injectionMap.clear();
 }
Jonathan S. Fisher
fonte