spring 调用 mock 方法时 mockito 测试失败
spring mockito test failed when call a mock method
这里有两个SpringService
;
public interface Service01 {
String test01(String name);
}
@Service
public class Service01Impl implements Service01 {
@Override
public String test01(String name) {
if (!"123".equals(name)) {
throw new RuntimeException("name is illegal");
}
return name;
}
}
@Service
public class Service02 {
@Autowired
private Service01 service01;
@Autowired
private Service03 service03;
public String test02(String name) {
service03.checkNameNotNull(name);
return service01.test01(name);
}
}
@Service
public class Service03 {
public void checkNameNotNull(String name) {
if (name == null) {
throw new RuntimeException("name is null");
}
}
}
然后这是我的测试类:
public class TestCalss {
@Mock
private Service01 service01;
@Autowired
private Service02 service02;
@Test
public void testMock() {
// mock service
when(service01.name("abc")).thenReturn("mock return abc");
// here will be success
String nameAbc = service01.test01("abc")
Assert.assertEquals("mock return abc", nameAbc);
// but here got exception: `name is illegal`
String name = service02.test02("abc");
Assert.assertEquals("mock return abc", name);
}
}
Q1:
为什么在行中出现异常:String name = service02.test02("abc");
java.lang.RuntimeException: name is illegal
Q2:调用Service02
时如何模拟Service01
?
-- 编辑
Q2 固定为 @InjectMocks
Q3:
如何只模拟 Service01
,但 Service03
没有模拟。
我想让这张支票没有模拟
service03.checkNameNotNull(name);
您需要在设置模拟时使用 ArgumentMatcher - 而不是传入字符串文字 "abc"
,您需要您的行看起来像 when(service01.name(eq("abc"))).thenReturn...
如果您不介意传入的实际字符串是什么,那么您还可以使用其他匹配器,例如 anyString()
。
您也不想在测试中使用 @Autowired
- 如果您希望被测试的 class 自动注入模拟,那么您需要实例化模拟并让它们生效注入而不是真正的 Spring bean。
最直接的方法是使用 MockitoJUnitRunner
和 @InjectMocks
注释:
@RunWith(MockitoJUnitRunner.class)
public class TestCalss {
@Mock
private Service01 service01;
@InjectMocks
private Service02 service02;
...
要注入不是模拟的 class,您需要使用 @Before
注释,通过某种方式将对象传递到 class(就像您在普通 Java 代码中)。
我的首选方法是使用构造函数将依赖项设置为 class 上的私有最终字段,但 Spring 还提供 class ReflectionTestUtils
如果你真的想坚持使用没有 setter 的私有字段,可以使用它。
所以像这样:
@Service
public class Service02 {
private final Service01 service01;
private final Service03 service03;
@Autowired
public Service02 (Service01 service01, Service03 service03) {
this.service01 = service01;
this.service03 = service03;
}
...
}
@RunWith(MockitoJUnitRunner.class)
public class TestClass {
@Mock
private Service01 service01;
private Service02 underTest;
@Before
public void setup() {
this.underTest = new Service02(this.service01, new Service03());
}
...
}
如果你想在一个你想要模拟的对象上调用一个真实的方法,那么这可能表明你的 class 做得太多了,实际上应该小两个或更多 classes,但也有可能:when(someMock.someMethod()).thenCallRealMethod();
这里有两个SpringService
;
public interface Service01 {
String test01(String name);
}
@Service
public class Service01Impl implements Service01 {
@Override
public String test01(String name) {
if (!"123".equals(name)) {
throw new RuntimeException("name is illegal");
}
return name;
}
}
@Service
public class Service02 {
@Autowired
private Service01 service01;
@Autowired
private Service03 service03;
public String test02(String name) {
service03.checkNameNotNull(name);
return service01.test01(name);
}
}
@Service
public class Service03 {
public void checkNameNotNull(String name) {
if (name == null) {
throw new RuntimeException("name is null");
}
}
}
然后这是我的测试类:
public class TestCalss {
@Mock
private Service01 service01;
@Autowired
private Service02 service02;
@Test
public void testMock() {
// mock service
when(service01.name("abc")).thenReturn("mock return abc");
// here will be success
String nameAbc = service01.test01("abc")
Assert.assertEquals("mock return abc", nameAbc);
// but here got exception: `name is illegal`
String name = service02.test02("abc");
Assert.assertEquals("mock return abc", name);
}
}
Q1:
为什么在行中出现异常:String name = service02.test02("abc");
java.lang.RuntimeException: name is illegal
Q2:调用Service02
时如何模拟Service01
?
-- 编辑
Q2 固定为 @InjectMocks
Q3:
如何只模拟 Service01
,但 Service03
没有模拟。
我想让这张支票没有模拟
service03.checkNameNotNull(name);
您需要在设置模拟时使用 ArgumentMatcher - 而不是传入字符串文字 "abc"
,您需要您的行看起来像 when(service01.name(eq("abc"))).thenReturn...
如果您不介意传入的实际字符串是什么,那么您还可以使用其他匹配器,例如 anyString()
。
您也不想在测试中使用 @Autowired
- 如果您希望被测试的 class 自动注入模拟,那么您需要实例化模拟并让它们生效注入而不是真正的 Spring bean。
最直接的方法是使用 MockitoJUnitRunner
和 @InjectMocks
注释:
@RunWith(MockitoJUnitRunner.class)
public class TestCalss {
@Mock
private Service01 service01;
@InjectMocks
private Service02 service02;
...
要注入不是模拟的 class,您需要使用 @Before
注释,通过某种方式将对象传递到 class(就像您在普通 Java 代码中)。
我的首选方法是使用构造函数将依赖项设置为 class 上的私有最终字段,但 Spring 还提供 class ReflectionTestUtils
如果你真的想坚持使用没有 setter 的私有字段,可以使用它。
所以像这样:
@Service
public class Service02 {
private final Service01 service01;
private final Service03 service03;
@Autowired
public Service02 (Service01 service01, Service03 service03) {
this.service01 = service01;
this.service03 = service03;
}
...
}
@RunWith(MockitoJUnitRunner.class)
public class TestClass {
@Mock
private Service01 service01;
private Service02 underTest;
@Before
public void setup() {
this.underTest = new Service02(this.service01, new Service03());
}
...
}
如果你想在一个你想要模拟的对象上调用一个真实的方法,那么这可能表明你的 class 做得太多了,实际上应该小两个或更多 classes,但也有可能:when(someMock.someMethod()).thenCallRealMethod();