1

ApplicationContext我有一个 Springboot 应用程序,它根据用户传递的输入参数在运行时查找 bean 。对于这种方法,我正在尝试编写 Mockito 测试用例,但它不起作用并抛出 NullPointerException。

引导应用程序的类:

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

我试图为其编写测试用例的类:

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

每当我运行测试用例时,它都会processor.setCommand(MyApplication.getApplicationContext().getBean(object.getAction(), Command.class));Mailbox课堂上给出 NullPointerException。如何模拟 ApplicationContext 查找?我错过了任何嘲笑步骤吗?

4

3 回答 3

2

不能肯定地说没有调试,但看起来MyApplication.getApplicationContext()正在返回null

而不是将它存储在静态变量中,您应该尝试在需要它ApplicationContext的类中注入:@Service

@Autowired
private ApplicationContext appContext;
于 2020-08-10T05:33:22.880 回答
2

Spring 明智的是,您的代码看起来不太好,尤其是不可单元测试。我会解释:

  1. 您的Mailbox服务不应该MyApplication在任何级别上都知道。它是 Spring Boot 应用程序的入口点,您的业务逻辑不应依赖于此。确实可以将应用程序上下文直接注入到类中。请参阅下面的示例。这里的另一个(更“老派”)选项是ApplicationContextAware在服务中使用接口Mailbox(参见这个例子)。然而,它仍然是一个糟糕的代码 IMO:
@Service
public class Mailbox {
 private final ApplicationContext ctx;
 ...
 public Mailbox(ApplicationContext ctx) {
     this.ctx = ctx;
 }
 ...
}
  1. 即使您解决了它,通常也依赖 ApplicationContext 也不是一个好主意。因为这样你就变得依赖于 spring 并且没有理由在 Mailbox 类中这样做。不过,该类将成为可单元测试的。

  2. 在分辨率方面:

在 spring 中,您可以将 aMap<String, Command>注入到邮箱中(它是 spring 中的一个内置功能),以便 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)用于运行单元测试(根本没有弹簧)。将此注释与@SpringBootTest运行完整的系统测试一起放置是没有意义的:启动整个 Spring Boot 应用程序,加载配置等等。

因此,请确保您要运行什么样的测试并使用适当的注释。

于 2020-08-10T06:41:25.983 回答
1

在第一次测试之前尝试通过注入处理器来初始化邮箱对象。

邮箱 = 新邮箱(处理器);

于 2020-08-10T05:49:20.467 回答