@ Значение в поле в @Service, введенном с помощью @MockBean, приводит к нулевому значению - PullRequest
0 голосов
/ 13 марта 2019

У меня есть эта услуга:

@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`
}

И этот интеграционный тест:

@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()
    }
}

Проблема в том, что когда myService.someMethodWhichUsesMyValueField() вызывается из метода MyController, вызываемого из метода someTest(), поле myValue (и все поля, помеченные @Autowired) равны null, даже если мой application.properties правильно определяет my.value=some value.

Есть ли способ правильно заставить myValue ввести значение, описанное в application.properties, как и любой обычный @Autowired компонент?

Ответы [ 3 ]

0 голосов
/ 13 марта 2019

Используйте @SpyBean вместо @MockBean, потому что тогда вам будет введен реальный объект Spring с помощью какого-то метода, который вы можете смоделировать (спасибо за @ thomas-andolf за @Spyподсказка):

// [...] 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
}

Другой (некрасивый?) способ - ввести поле вручную непосредственно из класса теста, например, так:

// [...] 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
}
0 голосов
/ 13 марта 2019

Чтобы решить эту проблему, можно воспользоваться следующими способами:

Конструктор инъекций:

@Service
public class MyService {

    private final myValue;

    public MyService(@Value("${my.value}") myValue) {
        this.myValue = myValue;
    }

    public String someMethodWhichUsesMyValueField() {
        return myValue;
    }
}

И вместо использования макета, просто используйте класс и введите ему значение.

Или вы можете использовать:

ReflectionTestUtils.setField(myService, "myValue", "FooBar");

чтобы установить для свойства myValue значение foobar.

Или вы можете изменить его, чтобы использовать вместо него @Spy, что является «частичной насмешкой». Шпион - это исходный класс с его оригинальными методами, и тогда вы можете выбрать имитацию некоторых методов, но в некоторых методах сохранить логику от исходного класса.

0 голосов
/ 13 марта 2019

Вы можете установить свои свойства в тестовом классе

@TestPropertySource(properties = {"my.value=value"})
public class MyControllerTest {
//
}
...