Eu sugiro que você refatorar seu código um pouco. Quando você começa a pensar em usar reflexão ou outro tipo de coisa, apenas para testar seu código, algo está errado com seu código.
Você mencionou diferentes tipos de problemas. Vamos começar com campos particulares. No caso de campos particulares, eu teria adicionado um novo construtor e injetado campos nele. Em vez disso:
public class ClassToTest {
private final String first = "first";
private final List<String> second = new ArrayList<>();
...
}
Eu teria usado isso:
public class ClassToTest {
private final String first;
private final List<String> second;
public ClassToTest() {
this("first", new ArrayList<>());
}
public ClassToTest(final String first, final List<String> second) {
this.first = first;
this.second = second;
}
...
}
Isso não será um problema, mesmo com algum código legado. O código antigo usará um construtor vazio e, se você me perguntar, o código refatorado parecerá mais limpo e você poderá injetar os valores necessários no teste sem reflexão.
Agora sobre métodos privados. Na minha experiência pessoal, quando você precisa stubar um método privado para teste, esse método não tem nada a ver nessa classe. Um padrão comum, nesse caso, seria envolvê- lo em uma interface, como Callable
e você passará nessa interface também no construtor (com esse truque de vários construtores):
public ClassToTest() {
this(...);
}
public ClassToTest(final Callable<T> privateMethodLogic) {
this.privateMethodLogic = privateMethodLogic;
}
Principalmente tudo o que escrevi parece ser um padrão de injeção de dependência. Na minha experiência pessoal, é realmente útil durante os testes, e acho que esse tipo de código é mais limpo e será mais fácil de manter. Eu diria o mesmo sobre classes aninhadas. Se uma classe aninhada contiver lógica pesada, seria melhor se você a tivesse movido como uma classe privada de pacote e a injetado em uma classe que precisa.
Também existem vários outros padrões de design que eu usei ao refatorar e manter o código legado, mas tudo depende dos casos do seu código para testar. Usar a reflexão principalmente não é um problema, mas quando você tem um aplicativo corporativo que é fortemente testado e os testes são executados antes de cada implantação, tudo fica realmente lento (é irritante e eu não gosto desse tipo de coisa).
Também há injeção de setter, mas eu não recomendaria usá-lo. É melhor ficar com um construtor e inicializar tudo quando for realmente necessário, deixando a possibilidade de injetar dependências necessárias.