J'ai ce service:
@RunWith(SpringRunner.class) @SpringBootTest(classes = MyApplication.class) public class MyControllerTest { private MockMvc myControllerMockMvc; @Autowired private MyController myController; // This controller injects an instance of MyService @MockBean private MyService myServiceMock; @Before public void setup() { this.myControllerMockMvc = MockMvcBuilders.standaloneSetup(myController).build(); when(myServiceMock.someMethodWhichUsesMyValueField()).thenCallRealMethod(); // Also mock the other methods in myServiceMock that call the external webservices } @Test public void someTest() throws Exception { // use the myControllerMockMvc to call a POST method that calls myService.someMethodWhichUsesMyValueField() } }
Et ce test d'intégration:
@Service public class MyService { @Value("${my.value}") private String myValue; public String someMethodWhichUsesMyValueField() { return myValue; } // Also contains other methods that use some external services accessed with a `RestTemplate` }
Le problème est que lorsque myService.someMethodWhichUsesMyValueField ()
est appelé depuis la méthode MyController
appelée depuis la méthode someTest ()
, le champ myValue
(et tout le champ annoté avec @Autowired
) est null
même si mon application.properties
définit correctement my.value = some value
.
Existe-t-il un moyen pour que myValue
injecte correctement la valeur décrite dans application.properties
comme n'importe quel composant @Autowired
normal ?
3 Réponses :
Vous pouvez définir vos propriétés dans une classe de test
@TestPropertySource(properties = {"my.value=value"}) public class MyControllerTest { // }
J'ai essayé d'ajouter cette annotation sur mon MyControllerTest
sans aucun effet: myValue
à l'intérieur de myService
était toujours null
.
@AnthonyO. vous utilisez @MockBean MyService myServiceMock
. Cela signifie que le ressort crée une maquette au lieu d'un objet. Vous devez le filer automatiquement
OK mais ce n'est pas votre réponse, n'est-ce pas? J'ai essayé d'ajouter @Autowired
sur le champ myServiceMock
dans MyControllerTest
sans plus de succès. J'ai aussi essayé d'ajouter un champ @Autowired MyService myService
en plus de mon @MockBean MyService myServiceMock
mais alors ces 2 objets sont la même instance: le simulé.
Utilisez un @SpyBean
au lieu d'un @MockBean
car vous aurez alors le véritable objet Spring injecté avec une méthode que vous pouvez vous moquer (merci pour réponse de @thomas-andolf pour l'astuce @Spy
):
// [...] Nothing to change in annotations public class MyControllerTest { // [...] Nothing to change in the other fields @Value("${my.value}") private String myValue; @Before public void setup() throws NoSuchFieldException, IllegalAccessException { ReflectionTestUtils.setField(myServiceMock, "myValue", myValue); // thanks to @thomas-andolf's [answer](https://stackoverflow.com/a/55148170/535203) // // Equivalent with traditional reflection tools: // Field myValueField = MyService.class.getDeclaredField("myValue"); // myValueField.setAccessible(true); // myValueField.set(myServiceMock, myValue); // [...] the rest of the original method } // [...] Nothing to change in the test methods }
L'autre façon (moche?) Est d'injecter le champ manuellement directement depuis la classe de test, comme ceci par exemple:
// [...] Nothing to change in annotations public class MyControllerTest { // [...] Nothing to change in the other fields @SpyBean private MyService myServiceMock; @Before public void setup() throws NoSuchFieldException, IllegalAccessException { // [...] Nothing to change before mocking // Then, do not mock anymore with .thenCallRealMethod() which is the default behavior with the spy // And when mocking the other methods in myServiceMock that call the external webservices, use this kind of declaration: doAnswer(invocation -> /* reply something */).when(myServiceMock).someMethodWhichUsesExternalWebService(any()); // instead of when(myServiceMock.someMethodWhichUsesExternalWebService(any())).thenAnswer(invocation -> /* reply something */); } // [...] Nothing to change in the test methods }
Voici quelques façons de résoudre ce problème, vous pouvez soit utiliser:
Injection de constructeur:
ReflectionTestUtils.setField(myService, "myValue", "FooBar");
Et au lieu d'utiliser un simulacre, utilisez simplement la classe et lui donner sa valeur.
Ou vous pouvez utiliser:
@Service public class MyService { private final myValue; public MyService(@Value("${my.value}") myValue) { this.myValue = myValue; } public String someMethodWhichUsesMyValueField() { return myValue; } }
pour définir la propriété myValue sur foobar.
Ou vous pouvez le changer pour utiliser un @Spy à la place, qui est une "simulation partielle". Un espion est la classe d'origine avec ses méthodes originales et vous pouvez alors choisir de vous moquer de certaines méthodes mais de conserver la logique de la classe d'origine dans certaines méthodes.
@Samim non cela ne fonctionne pas car ici
myServiceMock
est injecté avec@MockBean
qui ne parvient pas à inclure les valeurs@Value