環境
- Java 11.02
- Spring Boot 2.4.9
- powermock-module-junit4:2.0.9
- powermock-api-mockito2:2.0.7
概要
@RunWith(PowerMockRunner.class)
と@RunWith(SpringRunner.class)
を同時に使いたい。
しかし、@RunWith
には複数クラスを指定することはできない。
そのため、以下のように@PowerMockRunnerDelegate
を使う。
dependencies {
testImplementation 'org.powermock:powermock-module-junit4:2.0.9'
testImplementation 'org.powermock:powermock-api-mockito2:2.0.7'
}
@PrepareForTest({SampleServiceImpl.class})
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@SpringBootTest
public class SampleServiceImplTest {
}
しかしこれでテストを実行すると以下のエラーが出る。
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: java.security.NoSuchAlgorithmException: class configured for TrustManagerFactory: sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory not a TrustManagerFactory
以下の記事では、必要なconfigurationだけ読むようにすれば解決するだろうと書いてあるが、面倒くさい。
https://stackoverflow.com/questions/50307402/exception-using-springrunner-with-powermockrunner
解決
以下の記事の通りに、@PowerMockIgnore({ "javax.net.ssl.*" })
を付けたらエラーは出なくなり、テストが通るようになった。
https://www.nerd.vision/post/nosuchalgorithmexception-when-using-powermockito
@PrepareForTest({ SampleServiceImpl.class })
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@PowerMockIgnore({ "javax.net.ssl.*" })
@SpringBootTest
public class SampleServiceImplTest {
@Test
public void mainTest() throws Exception {
SampleServiceImpl mockInstance = PowerMockito.spy(new SampleServiceImpl());
PowerMockito.when(mockInstance, MemberMatcher.method(SampleServiceImpl.class, "private_method_name"))
.withArguments(Mockito.anyInt()).thenReturn(0);
mockInstance.main(Mockito.anyString());
assertEquals(0, mockInstance.private_method_name());
}
}
まだ問題が
PowerMockitoを使ってprivateメソッドをテストする際、そのメソッド内で、Autowiredされた他のクラスのオブジェクトが呼ばれていると、そいつがjava.lang.NullPointerException
を起こす。
以下にコードの例を記載する。
テスト対象のクラス
@Service
public class SampleServiceImpl implementes SampleService {
@Autowired
SampleRepository sampleRepository;
public void main(String message) {
System.out.println(fetchAll());
}
// ここをMockにしたい
private List<SampleModel> fetchAll() {
// これがNullPointerExceptionを起こす。そもそもMockにしてるのに何でこいつが呼び出されるの?
return sampleRepository.findAll();
}
}
Repository
public interface SampleRepository extends JpaRepository<SampleModel, Long> {
}
Model
@Getter
@Entity
@Table(name="sample_tables")
public class SampleModel {
@Id
private Long id;
private String text;
private Boolean status;
}
テストコード
@PrepareForTest({ SampleServiceImpl.class })
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@PowerMockIgnore({ "javax.net.ssl.*" })
@SpringBootTest
public class SampleServiceImplTest {
@Test
public void mainTest() throws Exception {
List<SampleModel> SamplesMock = SamplesMock();
// spyにしているが、mockでも結果は同じ
SampleServiceImpl SampleServiceImpl = PowerMockito.spy(new SampleServiceImpl());
// 以下の行でjava.lang.NullPointerExceptionが発生する
PowerMockito.doReturn(SamplesMock).when(SampleServiceImpl, "fetchAll");
}
}
原因と解決方法
アノテーションが問題であった。
@RunWith(PowerMockRunner.class)
と@SpringBootTest
が共存しているとこの問題が起きる。
そこで、以下のように@PowerMockIgnore
にjavax.xml.*
とorg.xml.*
を追記することで解決した。
@PrepareForTest({SampleServiceImpl.class})
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@PowerMockIgnore({
"javax.net.ssl.*",
"javax.xml.*",
"org.xml.*"
})
@SpringBootTest
public class SampleServiceImplTest {
}
以下にIssueが上がっているが、Powermock 2.0.0とjdk9以上で起こるバグっぽい。。