Mockito - 模拟 ApplicationContext
Mockito - mock ApplicationContext
我有一个 Springboot 应用程序,它根据用户传递的输入参数在 运行 时间从 ApplicationContext
查找 bean。对于这种方法,我正在尝试编写 Mockito 测试用例,但它不起作用并抛出 NullPointerException。
引导应用程序的class:
@SpringBootApplication
public class MyApplication {
private static ApplicationContext appContext;
public static void main(String[] args) {
appContext = SpringApplication.run(MyApplication.class, args);
}
public static ApplicationContext getApplicationContext() {
return appContext;
}
}
Class 我正在尝试为其编写测试用例:
@Service
public class Mailbox {
@Autowired
MailProcessor processor;
public void processUserInput(Envelope object) {
processor.setCommand(MyApplication.getApplicationContext().getBean(object.getAction(), Command.class));
processor.allocateEnvelopes(object);
}
}
我的测试用例如下:
@RunWith(MockitoJUnitRunner.class)
@SpringBootTest
@ActiveProfiles("test")
public class MailboxTest {
@Mock
MailProcessor processor;
@InjectMocks
Mailbox mailbox;
@Test
public void testProcessUserInput() {
Envelope message = new Envelope();
message.setAction("userAction");
message.setValue("userInput");
doNothing().when(processor).setCommand(any());
doNothing().when(processor).allocateEnvelopes(any());
mailbox.processUserInput(message);
Mockito.verify(processor).allocateEnvelopes(any());
}
}
每当我 运行 测试用例时,它都会在 Mailbox
class 中的 processor.setCommand(MyApplication.getApplicationContext().getBean(object.getAction(), Command.class));
处给出 NullPointerException。我如何模拟 ApplicationContext 查找?我是否遗漏了任何模拟步骤?
不调试就不能确定,但看起来 MyApplication.getApplicationContext()
正在返回 null
。
与其将其存储在静态变量中,不如尝试在 @Service
class 中需要的地方注入 ApplicationContext
:
@Autowired
private ApplicationContext appContext;
尝试在第一次测试之前通过注入处理器来初始化邮箱对象。
邮箱=新邮箱(处理器);
Spring 你的代码看起来不太好,尤其是不可单元测试。我来解释一下:
- 您的
Mailbox
服务在任何级别都不应知道 MyApplication
。它是 spring 引导应用程序的入口点,您的业务逻辑不应依赖于此。
确实可以将应用程序上下文直接注入 class。请参见下面的示例。此处的另一个(更多“old-school”)选项是在 Mailbox
服务中使用 ApplicationContextAware
接口(参见 this example)。然而,它仍然是一个糟糕的代码 IMO:
@Service
public class Mailbox {
private final ApplicationContext ctx;
...
public Mailbox(ApplicationContext ctx) {
this.ctx = ctx;
}
...
}
即使您解决了它,通常也依赖于 ApplicationContext 不是一个好主意。因为这样你就变得 spring 依赖,没有理由在邮箱 class 中这样做。 class 将成为可单元测试的。
分辨率方面:
在spring中你可以在邮箱中注入一个Map<String, Command>
(它是spring中的一个built-in特性)这样map的key就是一个bean名字,正是你信封的一个动作。
所以这里给出解决方案(在与注入无关的地方进行了简化,只是为了说明思路):
public interface Command {
void execute();
}
@Component("delete") // note this "delete" word - it will be a key in the map in the Mailbox
public class DeleteMailCommand implements Command {
@Override
public void execute() {
System.out.println("Deleting email");
}
}
@Component("send")
public class SendMailCommand implements Command{
@Override
public void execute() {
System.out.println("Sending Mail");
}
}
请注意,所有命令都必须由 spring 驱动(无论如何,这似乎是您的情况)。
现在,Mailbox
将如下所示:
@Service
public class Mailbox {
private final Map<String, Command> allCommands;
private final MailProcessor processor;
// Note this map: it will be ["delete" -> <bean of type DeleteMailCommand>, "send" -> <bean of type SendMailCommand>]
public Mailbox(Map<String, Command> allCommands, MailProcessor mailProcessor) {
this.allCommands = allCommands;
this.processor = mailProcessor;
}
public void processUserInput(Envelope envelope) {
Command cmd = allCommands.get(envelope.getAction());
processor.executeCommand(cmd);
}
}
此解决方案很容易进行单元测试,因为您可以根据需要使用模拟命令填充地图,而无需处理应用程序上下文。
更新
我现在看了你的测试,也不是很好,抱歉:)
@RunWith(MockitoJUnitRunner.class)
用于 运行 单元测试(根本没有 spring)。将此注释与 @SpringBootTest
一起放置是没有意义的,运行 是一个 full-fledged 系统测试:启动整个 spring 引导应用程序,加载配置等等。
因此请确定您想要进行哪种测试 运行 并使用适当的注释。
我有一个 Springboot 应用程序,它根据用户传递的输入参数在 运行 时间从 ApplicationContext
查找 bean。对于这种方法,我正在尝试编写 Mockito 测试用例,但它不起作用并抛出 NullPointerException。
引导应用程序的class:
@SpringBootApplication
public class MyApplication {
private static ApplicationContext appContext;
public static void main(String[] args) {
appContext = SpringApplication.run(MyApplication.class, args);
}
public static ApplicationContext getApplicationContext() {
return appContext;
}
}
Class 我正在尝试为其编写测试用例:
@Service
public class Mailbox {
@Autowired
MailProcessor processor;
public void processUserInput(Envelope object) {
processor.setCommand(MyApplication.getApplicationContext().getBean(object.getAction(), Command.class));
processor.allocateEnvelopes(object);
}
}
我的测试用例如下:
@RunWith(MockitoJUnitRunner.class)
@SpringBootTest
@ActiveProfiles("test")
public class MailboxTest {
@Mock
MailProcessor processor;
@InjectMocks
Mailbox mailbox;
@Test
public void testProcessUserInput() {
Envelope message = new Envelope();
message.setAction("userAction");
message.setValue("userInput");
doNothing().when(processor).setCommand(any());
doNothing().when(processor).allocateEnvelopes(any());
mailbox.processUserInput(message);
Mockito.verify(processor).allocateEnvelopes(any());
}
}
每当我 运行 测试用例时,它都会在 Mailbox
class 中的 processor.setCommand(MyApplication.getApplicationContext().getBean(object.getAction(), Command.class));
处给出 NullPointerException。我如何模拟 ApplicationContext 查找?我是否遗漏了任何模拟步骤?
不调试就不能确定,但看起来 MyApplication.getApplicationContext()
正在返回 null
。
与其将其存储在静态变量中,不如尝试在 @Service
class 中需要的地方注入 ApplicationContext
:
@Autowired
private ApplicationContext appContext;
尝试在第一次测试之前通过注入处理器来初始化邮箱对象。
邮箱=新邮箱(处理器);
Spring 你的代码看起来不太好,尤其是不可单元测试。我来解释一下:
- 您的
Mailbox
服务在任何级别都不应知道MyApplication
。它是 spring 引导应用程序的入口点,您的业务逻辑不应依赖于此。 确实可以将应用程序上下文直接注入 class。请参见下面的示例。此处的另一个(更多“old-school”)选项是在Mailbox
服务中使用ApplicationContextAware
接口(参见 this example)。然而,它仍然是一个糟糕的代码 IMO:
@Service
public class Mailbox {
private final ApplicationContext ctx;
...
public Mailbox(ApplicationContext ctx) {
this.ctx = ctx;
}
...
}
即使您解决了它,通常也依赖于 ApplicationContext 不是一个好主意。因为这样你就变得 spring 依赖,没有理由在邮箱 class 中这样做。 class 将成为可单元测试的。
分辨率方面:
在spring中你可以在邮箱中注入一个Map<String, Command>
(它是spring中的一个built-in特性)这样map的key就是一个bean名字,正是你信封的一个动作。
所以这里给出解决方案(在与注入无关的地方进行了简化,只是为了说明思路):
public interface Command {
void execute();
}
@Component("delete") // note this "delete" word - it will be a key in the map in the Mailbox
public class DeleteMailCommand implements Command {
@Override
public void execute() {
System.out.println("Deleting email");
}
}
@Component("send")
public class SendMailCommand implements Command{
@Override
public void execute() {
System.out.println("Sending Mail");
}
}
请注意,所有命令都必须由 spring 驱动(无论如何,这似乎是您的情况)。
现在,Mailbox
将如下所示:
@Service
public class Mailbox {
private final Map<String, Command> allCommands;
private final MailProcessor processor;
// Note this map: it will be ["delete" -> <bean of type DeleteMailCommand>, "send" -> <bean of type SendMailCommand>]
public Mailbox(Map<String, Command> allCommands, MailProcessor mailProcessor) {
this.allCommands = allCommands;
this.processor = mailProcessor;
}
public void processUserInput(Envelope envelope) {
Command cmd = allCommands.get(envelope.getAction());
processor.executeCommand(cmd);
}
}
此解决方案很容易进行单元测试,因为您可以根据需要使用模拟命令填充地图,而无需处理应用程序上下文。
更新
我现在看了你的测试,也不是很好,抱歉:)
@RunWith(MockitoJUnitRunner.class)
用于 运行 单元测试(根本没有 spring)。将此注释与 @SpringBootTest
一起放置是没有意义的,运行 是一个 full-fledged 系统测试:启动整个 spring 引导应用程序,加载配置等等。
因此请确定您想要进行哪种测试 运行 并使用适当的注释。