現象
下記のようなクラス構成のテストを書いたところ、TestRestTemplate が Autowired できない現象が発生しました。
# AP は 9000 番ポートで動作させる
server.port=9000
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ContextConfiguration(classes = HogeApplication.class)
public abstract class AbstractBaseTest {
protected MockMvc mvc;
@Autowired
protected WebApplicationContext context;
@Autowired
protected TestRestTemplate restTemplate;
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = HogeApplication.class)
public class SubTestClass extends AbstractBaseTest {
something tests...
public void URLをテストする() throws Exception {
HashMap param = new HashMap<String, String>();
param.put("userAccount", ユーザーアカウント);
param.put("password", パスワード);
ResponseEntity<String> resultResponse = restTemplate.postForEntity("http://localhost:9000/authentication", param, String.class);
}
}
ログの内容
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.boot.test.web.client.TestRestTemplate' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1493)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1104)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
... 28 more
回避策
サブクラスにて webEnvironment に RANDOMPORT、親クラスなどで@LocalServerPortを指定するです。
Spring Boot のページ のサンプルでは、LocalServerPort の指定があり、テストをするにあたり事実上不可欠のアノテーションですが、TestRestTemplate の生成については不要のようです。ドキュメントにも必須かどうかについての言及がありません。
Note the use of webEnvironment=RANDOM_PORT to start the server with a random port (useful to avoid conflicts in test environments), and the injection of the port with @LocalServerPort. Also note that Spring Boot has provided a TestRestTemplate for you automatically, and all you have to do is @Autowired it.
2017/08 現在の動作としては、
- 使用するクラスにて TestRestTemplate を Autowired しないと Bean を生成できない。
- LocalServerPort のポートへ TestRestTemplate から要求しないと、Connection Refused になる。
のようです。
よって正しい構成は下記のようになるようです。
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) //あってもなくても良い
@ContextConfiguration(classes = HogeApplication.class)
public abstract class AbstractBaseTest {
protected MockMvc mvc;
@Autowired
protected WebApplicationContext context;
@Autowired
protected TestRestTemplate restTemplate;
@LocalServerPort
protected int serverPort;
}
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ContextConfiguration(classes = HogeApplication.class)
public class SubTestClass extends AbstractBaseTest {
something tests...
public void URLをテストする() throws Exception {
HashMap param = new HashMap<String, String>();
param.put("userAccount", ユーザーアカウント);
param.put("password", パスワード);
ResponseEntity<String> resultResponse = restTemplate.postForEntity("http://localhost:" + serverPort + "/authentication", param, String.class);
}
}
@LocalServerPort が存在しない場合、該当のアプリケーションにモックの HTTP 経由でアクセスできないようです。
ConnectionRefused のエラーが発生します。
org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://localhost:9000/hoge": Connection refused (Connection refused); nested exception is java.net.ConnectException: Connection refused (Connection refused)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:666)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613)
at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:407)
at org.springframework.boot.test.web.client.TestRestTemplate.postForEntity(TestRestTemplate.java:471)
at com.asaas.billing.controller.SigninControllerTest.URLをテストする(SigninControllerTest.java:72)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
Caused by: java.net.ConnectException: Connection refused (Connection refused)
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)