java - mvc - Inyectando campo privado @Autowired durante la prueba
spring java tutorial (6)
A veces puede refactorizar su @Component
para usar la inyección basada en el constructor o el instalador para configurar su caso de prueba (puede y aún puede confiar en @Autowired
). Ahora, puede crear su prueba completamente sin un marco de burla mediante la implementación de pruebas en su lugar (por ejemplo, MailServiceStub Martin Fowler):
@Component
public class MyLauncher {
private MyService myService;
@Autowired
MyLauncher(MyService myService) {
this.myService = myService;
}
// other methods
}
public class MyServiceStub implements MyService {
// ...
}
public class MyLauncherTest
private MyLauncher myLauncher;
private MyServiceStub myServiceStub;
@Before
public void setUp() {
myServiceStub = new MyServiceStub();
myLauncher = new MyLauncher(myServiceStub);
}
@Test
public void someTest() {
}
}
Esta técnica es especialmente útil si la prueba y la clase bajo prueba se ubican en el mismo paquete porque entonces puede usar el modificador de acceso predeterminado, package-private para evitar que otras clases accedan a él. Tenga en cuenta que aún puede tener su código de producción en src/main/java
pero sus pruebas en src/main/test
directorios src/main/test
.
Si te gusta Mockito, entonces apreciarás el MockitoJUnitRunner . Te permite hacer cosas "mágicas" como @Manuel te mostró:
@RunWith(MockitoJUnitRunner.class)
public class MyLauncherTest
@InjectMocks
private MyLauncher myLauncher; // no need to call the constructor
@Mock
private MyService myService;
@Test
public void someTest() {
}
}
Alternativamente, puede usar el corredor JUnit predeterminado y llamar a MockitoAnnotations.initMocks() en un método setUp()
para permitir que Mockito inicialice los valores anotados. Puede encontrar más información en el javadoc de @InitMocks y en una publicación de blog que he escrito.
Tengo una configuración de componentes que es esencialmente un iniciador para una aplicación. Está configurado así:
@Component
public class MyLauncher {
@Autowired
MyService myService;
//other methods
}
MyService está anotado con la anotación @Service
Spring y está conectado automáticamente a mi clase de iniciador sin ningún problema.
Me gustaría escribir algunos casos de prueba jUnit para MyLauncher, para hacerlo comencé una clase como esta:
public class MyLauncherTest
private MyLauncher myLauncher = new MyLauncher();
@Test
public void someTest() {
}
}
¿Puedo crear un objeto Mock para MyService e insertarlo en myLauncher en mi clase de prueba? Actualmente no tengo un getter o setter en myLauncher ya que Spring está manejando el autoenvío. Si es posible, me gustaría no tener que agregar getters y setters. ¿Puedo decirle al caso de prueba que inyecte un objeto simulado en la variable autocabilitada usando un método @Before
init?
Si me estoy equivocando, no dudes en decirlo. Todavía soy nuevo en esto. Mi objetivo principal es tener algún código Java o anotación que @Autowired
un objeto simulado en esa variable @Autowired
sin que tenga que escribir un método setter o tener que usar un archivo applicationContext-test.xml. Prefiero mantener todo para los casos de prueba en el archivo .java en lugar de tener que mantener un ApplicationContent por separado solo para mis pruebas.
Estoy esperando usar Mockito para los objetos simulados. En el pasado, he hecho esto usando org.mockito.Mockito
y creando mis objetos con Mockito.mock(MyClass.class)
Muchas gracias.
Creo que para poder tener el trabajo de auto-cableado en su clase MyLauncher (para myService), tendrá que dejar que Spring lo inicialice en lugar de llamar al constructor, al autoconectar myLauncher. Una vez que se está conectando automáticamente (y myService también se está autoconectando), Spring (1.4.0 y superior) proporciona una anotación @MockBean que puede poner en su prueba. Esto reemplazará a los beans individuales coincidentes en contexto con un simulacro de ese tipo. Luego puede definir qué burla quiere, en un método @Before.
public class MyLauncherTest
@MockBean
private MyService myService;
@Autowired
private MyLauncher myLauncher;
@Before
private void setupMockBean() {
doNothing().when(myService).someVoidMethod();
doReturn("Some Value").when(myService).someStringMethod();
}
@Test
public void someTest() {
myLauncher.doSomething();
}
}
Su clase MyLauncher puede permanecer sin modificaciones, y su bean MyService será un simulacro cuyos métodos devuelven valores como usted definió:
@Component
public class MyLauncher {
@Autowired
MyService myService;
public void doSomething() {
myService.someVoidMethod();
myService.someMethodThatCallsSomeStringMethod();
}
//other methods
}
Un par de ventajas de esto sobre otros métodos mencionados es que:
- No necesita insertar manualmente myService.
- No necesita usar el corredor Mockito o las reglas.
La respuesta aceptada (use MockitoJUnitRunner
y @InjectMocks
) es genial. Pero si quieres algo un poco más liviano (sin un corredor JUnit especial), y menos "mágico" (más transparente) especialmente para uso ocasional, puedes simplemente establecer los campos privados directamente usando introspección.
Si usa Spring, ya tiene una clase de utilidad para esto: org.springframework.test.util.ReflectionTestUtils
El uso es bastante sencillo:
ReflectionTestUtils.setField(myLauncher, "myService", myService);
El primer argumento es su bean objetivo, el segundo es el nombre del campo (generalmente privado) y el último es el valor para inyectar.
Si no usas Spring, es bastante trivial implementar dicho método de utilidad. Aquí está el código que utilicé antes de encontrar esta clase de primavera:
public static void setPrivateField(Object target, String fieldName, Object value){
try{
Field privateField = target.getClass().getDeclaredField(fieldName);
privateField.setAccessible(true);
privateField.set(target, value);
}catch(Exception e){
throw new RuntimeException(e);
}
}
Mira este link
Luego escribe tu caso de prueba como
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"/applicationContext.xml"})
public class MyLauncherTest{
@Resource
private MyLauncher myLauncher ;
@Test
public void someTest() {
//test code
}
}
Puede inyectar absolutamente simulaciones en MyLauncher en su prueba. Estoy seguro de que si muestras el marco de burla que estás usando, serías rápido para dar una respuesta. Con mockito buscaría usar @RunWith (MockitoJUnitRunner.class) y usar anotaciones para myLauncher. Se vería algo así como lo que está abajo.
@RunWith(MockitoJUnitRunner.class)
public class MyLauncherTest
@InjectMocks
private MyLauncher myLauncher = new MyLauncher();
@Mock
private MyService myService;
@Test
public void someTest() {
}
}
Soy un nuevo usuario para Spring. Encontré una solución diferente para esto. Usar la reflexión y hacer públicos los campos necesarios y asignar objetos falsos.
Este es mi controlador de autenticación y tiene algunas propiedades privadas de Autowired.
@RestController
public class AuthController {
@Autowired
private UsersDAOInterface usersDao;
@Autowired
private TokensDAOInterface tokensDao;
@RequestMapping(path = "/auth/getToken", method = RequestMethod.POST)
public @ResponseBody Object getToken(@RequestParam String username,
@RequestParam String password) {
User user = usersDao.getLoginUser(username, password);
if (user == null)
return new ErrorResult("Kullanıcıadı veya şifre hatalı");
Token token = new Token();
token.setTokenId("aergaerg");
token.setUserId(1);
token.setInsertDatetime(new Date());
return token;
}
}
Y esta es mi prueba de Junit para AuthController. Estoy haciendo públicas las propiedades privadas necesarias y les asigno objetos falsos y rock :)
public class AuthControllerTest {
@Test
public void getToken() {
try {
UsersDAO mockUsersDao = mock(UsersDAO.class);
TokensDAO mockTokensDao = mock(TokensDAO.class);
User dummyUser = new User();
dummyUser.setId(10);
dummyUser.setUsername("nixarsoft");
dummyUser.setTopId(0);
when(mockUsersDao.getLoginUser(Matchers.anyString(), Matchers.anyString())) //
.thenReturn(dummyUser);
AuthController ctrl = new AuthController();
Field usersDaoField = ctrl.getClass().getDeclaredField("usersDao");
usersDaoField.setAccessible(true);
usersDaoField.set(ctrl, mockUsersDao);
Field tokensDaoField = ctrl.getClass().getDeclaredField("tokensDao");
tokensDaoField.setAccessible(true);
tokensDaoField.set(ctrl, mockTokensDao);
Token t = (Token) ctrl.getToken("test", "aergaeg");
Assert.assertNotNull(t);
} catch (Exception ex) {
System.out.println(ex);
}
}
}
No conozco las ventajas y desventajas de esta manera, pero esto está funcionando. Esta técnica tiene un poco más de código, pero estos códigos se pueden separar por diferentes métodos, etc. Hay más buenas respuestas para esta pregunta, pero quiero apuntar a una solución diferente. Perdón por mi mal ingles. Que tengas un buen java para todos :)