2016-06-09 38 views
6

我在网上做了很多搜索,找不到一个使用自动布线构造函数进行单元测试的例子。我正在使用Spring将属性文件中的值自动装入到我的应用程序中。我想单元测试MyApp.java的启动方法,但我有一个自动装配的构造函数,所以我不知道如何实例化MyApp。如果没有自动装配性能,我在我的单元测试这样做:如何JUnit测试Spring自动布线构造函数?

@Test 
public void testStart() { 
    try{ 
    MyApp myApp = new MyApp(); 
    myApp.start(); 
    } 
    catch (Exception e){ 
    fail("Error thrown") 
    } 
} 

我不想嘲笑自动装配,因为我需要从属性文件,并进一步复杂化的东西来获得价值,我通过注释配置所有内容。我没有spring.xml,application-context.xml或web.xml文件。那么我该如何去实例化/测试MyApp的启动方法呢?我尝试在@RunWith(SpringJUnit4ClassRunner.class)中添加并自动装配MyApp myApp,但它会抛出有关未能通过在测试类上实现ApplicationContextAware来解决的应用程序上下文失败的错误。

这里是MyApp.java

@Component 
public class MyApp { 

    private static ApplicationContext applicationContext; 
    private static MyAppProperties myAppProperties; 

    //Obtain the values from the app.properties file 
    @Autowired 
    MyApp(MyAppProperties myAppProps){ 
     myAppProperties = myAppProps; 
    } 

    public static void main(String[] args) throws Exception { 
    // Instantiate the application context for use by the other classes 
    applicationContext = new AnnotationConfigApplicationContext("com.my.company"); 

    start(); 
    } 

    /** 
    * Start the Jetty server and configure the servlets 
    * 
    * @throws Exception 
    */ 
    public static void start() throws Exception { 
     // Create Embedded Jetty server 
     jettyServer = new Server(); 

     // Configure Jetty so that it stops at JVM shutdown phase 
     jettyServer.setStopAtShutdown(true); 
     jettyServer.setStopTimeout(7_000); 

     // Create a list to hold all of the handlers 
     final HandlerList handlerList = new HandlerList(); 

     // Configure for Http 
     HttpConfiguration http_config = new HttpConfiguration(); 
     http_config.setSecureScheme("https"); 
     http_config.setSecurePort(myAppProperties.getHTTP_SECURE_PORT()); 
    .... 
    } 
} 

这里是我的app.properties文件

# Spring Configuration for My application 

#properties for the embedded jetty server 
http_server_port=12345 

这里是MyAppProperties.java

@Component 
public class MyAppProperties implements ApplicationContextAware { 

    private ApplicationContext applicationContext; 

    //List of values from the properties files to be autowired 
    private int HTTP_SERVER_PORT; 
    ... 

    @Autowired 
    public MyAppProperties(@Value("${http_server_port}") int http_server_port, ...){ 
     this.HTTP_SERVER_PORT = http_server_port; 
    } 

    /** 
    * @return the applicationContext 
    */ 
    public ApplicationContext getApplicationContext() { 
     return applicationContext; 
    } 

    /** 
    * @param applicationContext 
    *   the applicationContext to set 
    */ 
    @Override 
    public void setApplicationContext(ApplicationContext applicationContext) { 
     this.applicationContext = applicationContext; 
    } 

    /** 
    * @param name 
    *   the name to set 
    */ 
    public void setHTTP_SERVER_PORT(String name) { 
     JETTY_SERVER_NAME = name; 
    } 

    /** 
    * @return the httpServerPort 
    */ 
    public int getHTTP_SERVER_PORT() { 
     return HTTP_SERVER_PORT; 
    } 
} 

这里是MyAppTest.java

@RunWith(SpringJUnit4ClassRunner.class) 
public class MyAppTest implements ApplicationContextAware{ 

    private ApplicationContext applicationContext; 

    @Override 
    public void setApplicationContext(ApplicationContext appContext) { 
     applicationContext = appContext;  
    } 

    @Autowired 
    private MyApp myapp; 

    @Test 
    public void testStart(){ 
    try { 
     if(myapp != null){ 
      myapp.start(); 
     } 
     else{ 
      fail("myapp is null"); 
     } 
    } catch (Exception e) { 
     fail("Error thrown"); 
     e.printStackTrace(); 
    } 
    } 
} 

更新:这里是我的配置类

@Configuration 
@Component 
public class ApplicationConfig implements ApplicationContextAware { 

    private final Logger LOGGER = LoggerFactory.getLogger(ApplicationConfig.class); 
    private ApplicationContext applicationContext; 

    /** 
    * @return the applicationContext 
    */ 
    public ApplicationContext getApplicationContext() { 
     LOGGER.debug("Getting Application Context", applicationContext); 
     return applicationContext; 
    } 

    /** 
    * @param applicationContext 
    *   the applicationContext to set 
    */ 
    @Override 
    public void setApplicationContext(ApplicationContext applicationContext) { 
     this.applicationContext = applicationContext; 
    } 

    // Needed for @Value 
    /** 
    * Property sources placeholder configurer. 
    * 
    * @return the property sources placeholder configurer 
    */ 
    @Bean 
    public PropertyPlaceholderConfigurer getPropertyPlaceholderConfigurer() { 
     PropertyPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertyPlaceholderConfigurer(); 
     propertyPlaceholderConfigurer.setLocation(new ClassPathResource("app.properties")); 
     return propertyPlaceholderConfigurer; 
    } 
    ... 
} 
+0

仅添加'@ RunWith'并且不告知要加载哪个配置将会失败。你必须在你的测试类中添加一个'public static class',在其上放置'@ Configuration'和'@ComponentScan(“your.package”)''。在更进一步的层面上,我会建议使用Spring Boot来引导应用程序并注入/使用属性,因为您似乎在做Spring Boot提供的开箱即用(包括测试支持)。 –

+0

@ M.Deinum不幸的是,由于与其他工具的冲突,Spring Boot不适合我。只是为了澄清你说我应该改变我的测试类是公共静态类MyAppTest还是我应该做另一个类?当我尝试使MyAppTest类静态时,我收到一条错误消息,指出它是非法修饰符。 – jencoston

+0

不,您需要添加内部配置类。另外,我并没有发现Spring Boot在与你自己一样笑内部服务器时会有什么不同,应该没有什么区别。 –

回答

6

我们可以使用jmockito框架嘲笑的对象。

为通过的Mockito依赖 使用注射您@InjectMocks也有@InjectMocks标注它试图基于该类型做构造函数,方法或字段依赖注入。以下代码是Javadoc稍加修改的示例。

// Mockito can construct this class via constructor 
public class ArticleManager { 
     ArticleManager(ArticleCalculator calculator, ArticleDatabase database) { 
     } 
} 

// Mockito can also perform method injection 
public class ArticleManager { 
     ArticleManager() { } 
     void setDatabase(ArticleDatabase database) { } 
     void setCalculator(ArticleCalculator calculator) { } 
} 

// Mockito can also perform field injection 
public class ArticleManager { 

    private ArticleDatabase database; 
    private ArticleCalculator calculator; 
} 

以下是单元测试类。

@RunWith(MockitoJUnitRunner.class) 
public class ArticleManagerTest { 
    @Mock private ArticleCalculator calculator; 
    @Mock private ArticleDatabase database; 
    @Spy private UserProvider userProvider = new ConsumerUserProvider(); 

    // creates instance of ArticleManager 
    // and performs constructor injection on it 
    @InjectMocks private ArticleManager manager; 

    @Test public void shouldDoSomething() { 
      // assume that ArticleManager has a method called initialize which calls a method 
      // addListener with an instance of ArticleListener 
      manager.initialize(); 

     // validate that addListener was called 
      verify(database).addListener(any(ArticleListener.class)); 
    } 

}

确保您使用的@RunWith(MockitoJUnitRunner.class) 欲了解更多信息,请参阅http://docs.mockito.googlecode.com/hg/1.9.5/org/mockito/InjectMocks.html

+0

谢谢你的回答!如果我嘲笑MyApp的构造函数,我仍然会从属性文件中获取值?我试图测试的方法启动嵌入式jetty服务器,它需要从属性文件中获取值来执行此操作。 – jencoston

+0

使用@InjectMocks被认为是不好的做法。例如,请参阅:https://lkrnac.net/blog/2014/02/promoting-constructor-field-injection/ – JonyD

+0

关于@JonyD评论:我已阅读了一些评论,其中大多数与文章相矛盾。你有没有找到一些最终的答案? – matthieusb