Java& Mockito - 将参数传递给方法并捕获结果

时间:2016-02-12 10:03:54

标签: java hibernate mockito

我试图修复一些现有的junit / mockito测试,由于重写使用Dao,我不得不改变。

所以我有这个论点的俘虏: -

ArgumentCaptor<CustomerDao> customerDaoCaptor = ArgumentCaptor.forClass(CustomerDao.class);

我之前使用过这种方法来获取(客户)对象,以便我可以对它进行更多测试。我通常会这样使用它: -

verify(customerDao, times(1)).saveOrUpdate(customerDaoCaptor.capture());    

这样我就可以运行如下测试: -

Customer customerActual = (Customer) customerDaoCaptor.getAllValues().get(0);
assertEquals("PRE", customerActual.getExistingCustomer());

但是在这种情况下,我没有调用saveOrUpdate方法(绑定器绑定),而是另一个Dao方法,它使用唯一键作为参数,最终通过使用sql更新客户记录 - 即不使用父对象的(Hibernate)saveOrUpdate方法。

我知道我可以测试它的被叫,例如: -

inOrder.verify(customerDao, times(1)).updateRegisterStatusToCurrentByCustomerNumber(CUSTOMER_NUMBER);

所以我试图以某种方式将绑定器分配/绑定到&quot; updateRegisterStatus ....&#39;方法,但我似乎无法找到一种方法,主要是因为该方法必须采用字符串参数,customer_number。

所以从本质上讲,我试图这样做: -

inOrder.verify(customerDao, times(1)).updateRegisterStatusToCurrentByCustomerNumber(CUSTOMER_NUMBER).customerDaoCaptor.capture()

显然不起作用......

由于很多谷歌搜索都没有帮助我,我猜测我完全错了。

更新 - @SpaceTrucker

我按照您的建议尝试了以下代码: -

CapturingMatcher<String> capturingMatcher = new CapturingMatcher<String>();
    verify(customerDao, times(1)).updateRegisterStatusToCurrentByCustomerNumber(
        argThat(
            org.hamcrest.Matchers.allOf(capturingMatcher, org.hamcrest.Matchers.notNullValue())
        )
    );
    List<String> values = capturingMatcher.getAllValues();

基于我的Dao实施: -

public void updateRegisterStatusToCurrentByCustomerNumber(String customerNumber)

它确实成功通过了测试,因为它没有失败,但它并没有做我需要的所有事情。这里的理想目标是以某种方式获得表示更新的客户对象的对象 - 例如: -

Customer customerActual = (Customer) values.get(0);
assertEquals("value", customerActual.getExistingCustomer());

但是,对象为空,在调试测试时,我可以确认正在调用相关方法。

如果我在这里错过了一些微不足道的事情,请提前道歉,再次感谢您的帮助!

2 个答案:

答案 0 :(得分:1)

这个问题似乎比最初想到的更难。有关详细信息,请参见下文。

Mockito需要查看Matcher实例必须实现CapturesArguments接口。所以解决方案是实现一个AndMatcher,它将委托给它的子匹配器并实现CapturesArguments。它将委托所有在CapturesArguments时同时实施CapturesArguments.captureFrom(Object)的儿童匹配器。请注意CapturesArguments是Mockito内部界面。

以下解决方案不起作用, 因为Mockito看到Matcher个实例,并没有实现CapturesArguments接口,因此无法将参数捕获委托给CapturingMatcher

ArgumentCaptor在内部使用CapturingMatcher。因此,您可以将Mockito.argThat与一个由CapturingMatcher和您喜欢的任何其他匹配器组成的组合匹配器一起使用。

例如给定接口

public interface ProductService {
    List<Product> getProductsForCategory(Category category);
}

然后我们可以做到以下几点:

import org.hamcrest.Matchers;

// ...    

CapturingMatcher<Category> capturingMatcher = new CapturingMatcher<Category>();
Mockito.verify(productService).getProductsForCategory(Mockito.argThat(Matchers.allOf(capturingMatcher, Matchers.notNullValue())));
List<Category> values = capturingMatcher.getAllValues();

您还可以使用ArgumentCaptor方法实现自己的capture,这将占用额外的匹配器实例。

答案 1 :(得分:0)

Your question isn't entirely clear, but I'm getting the following points from it:

  1. You have a mock of a package com.javacodegeeks.snippets.enterprise; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.Map; import java.util.Properties; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller @RequestMapping("/helloWorld") public class HelloWorldController { private final String layout = "ViewLayout"; private static enum Status{ RUNNING,ONHOLD,FINISHED,ERROR } Status status; //setup method @RequestMapping(value = "/setup/{username}/{password}/{host}/", method = RequestMethod.GET) public String Setup(@PathVariable("username") String username,@PathVariable("password") String pass,@PathVariable("host") String host ,ModelMap model) throws IOException { model.addAttribute("tag","Setup configuration"); OutputStream output = null; File myfile = new File(username+".properties"); try{ if(!checkIfFileExists(myfile)){ myfile.createNewFile(); output = new FileOutputStream(myfile,false); } else{ model.addAttribute("msg","Error: Setup Failed, configuration for the user ="+" "+username+" "+"already exists!"); return layout; } Properties prop = new Properties(); prop.setProperty("username", username); prop.setProperty("password", pass); prop.setProperty("host", host); prop.setProperty("status", status.FINISHED.toString()); prop.store(output, null); } catch (IOException io) { io.printStackTrace(); } finally { if (output != null) { try { output.close(); } catch (IOException e) { e.printStackTrace(); } } } model.addAttribute("msg","Configuration successfully updated!"); return layout; } //run service method @RequestMapping(value = "/run/{username}/{password}/", method = RequestMethod.GET) public String runService(@PathVariable("username") String username,@PathVariable("password") String pass,ModelMap model){ model.addAttribute("tag","Running service procedure"); File myfile = new File(username+".properties"); if(!checkIfFileExists(myfile)){ model.addAttribute("msg","Error: Run Failed, configuration for the user ="+" "+username+" "+"not found!"); return layout; } else{ int i=0; Properties prop = new Properties(); InputStream input = null; try { input = new FileInputStream(myfile); prop.load(input); if(!authenticatePassword(prop,pass)){ model.addAttribute("msg","Error: Run Failed, The password is inccorrect"); return layout; } } catch (IOException ex) { ex.printStackTrace(); } finally { if (input != null) { try { input.close(); } catch (IOException e) { e.printStackTrace(); } } } String stat = prop.getProperty("status"); if(stat.equals(status.FINISHED.toString()) || stat.equals(status.ONHOLD.toString())) { updateConfigfile(username+".properties","status",status.RUNNING.toString()); do{ i++; model.addAttribute("msg","inside loop"+" "+"Counter is:"+" "+i+" "+"status is =" +prop.getProperty("status")); }while(prop.getProperty("status").equals(status.RUNNING.toString())); }else{ model.addAttribute("msg","Service is already running"); } } return layout; } //get status @RequestMapping(value = "/getstatus/{username}/{password}/", method = RequestMethod.GET) public String getServiceSatus(@PathVariable("username") String username,@PathVariable("password") String pass,ModelMap model) { model.addAttribute("tag","Get Status"); Properties prop = loadProperties(username+".properties"); if(prop == null){ model.addAttribute("msg","Error: Status is not available: can not read properties file!"); return layout; } if(!authenticatePassword(prop,pass)){ model.addAttribute("msg","Error: Get status failed, password or username is inccorrect"); return layout; } String status = prop.getProperty("status"); model.addAttribute("msg", "Service status is:"+" "+status); return layout; } //stop service @RequestMapping(value = "/stop/{username}/{password}/", method = RequestMethod.GET) public String stopService( @PathVariable("username") String username,@PathVariable("password") String pass,ModelMap model) { String message = ""; Properties prop = loadProperties(username+".properties"); if(prop == null){ model.addAttribute("msg","Error: Can not stop service, properties file does not exist or username is inccorrect!"); return layout; } if(!authenticatePassword(prop,pass)){ model.addAttribute("msg","Error: Can not stop service, password or username is inccorrect"); return layout; } String stat = prop.getProperty("status"); if(stat.equals(status.RUNNING.toString())) { updateConfigfile(username+".properties","status",status.ONHOLD.toString()); message = "Service was stoped"; }else{ message = "service is not running status is = "+ " "+prop.getProperty("status"); } model.addAttribute("tag","Stop Service"); model.addAttribute("msg",message); return layout; } public boolean checkIfFileExists(File filename){ if(!filename.exists()){ return false; } return true; } //function that updating properties file public void updateConfigfile(String filename ,String key,String val){ FileInputStream in = null; Properties props = new Properties(); try { in = new FileInputStream(filename); props.load(in); in.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e1) { e1.printStackTrace(); } FileOutputStream out = null; try { out = new FileOutputStream(filename); props.setProperty(key, val); props.store(out, null); out.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } //function that load properties file public Properties loadProperties(String filename){ Properties prop = new Properties(); InputStream input = null; try { input = new FileInputStream(filename); prop.load(input); } catch (IOException ex) { ex.printStackTrace(); } finally { if (input != null) { try { input.close(); } catch (IOException e) { e.printStackTrace(); } } else{ return null; } } return prop; } public boolean authenticatePassword(Properties prop,String pass){ if(!(prop.getProperty("password").equals(pass))){ return false; } return true; } } .
  2. Your test (indirectly) calls the CustomerDao method of this mock, passing a updateRegisterStatusToCurrentByCustomerNumber() identifier to it.
  3. String does something internally to a updateRegisterStatusToCurrentByCustomerNumber() object.
  4. You want to test things about the Customer object in question.

If these are correct, then you have a fundamental misunderstanding of how mocks work. That internal code that does something to a Customer object? In this test, that code doesn't exist. It's never called. The mock version of Customer responds to your CustomerDao call by simply returning what your test setup code told it to, whether that's updateRegisterStatusToCurrentByCustomerNumber(), a carefully crafted sample object, or another mock. It never invokes the actual null code because the whole point of a mock is to make your test not be dependent on that code, so that bugs in that code don't cascade test failures throughout the dependency tree.

To test the internal behavior of CustomerDao, you will need to create separate tests that directly call CustomerDao.updateRegisterStatusToCurrentByCustomerNumber() methods on a non-mock CustomerDao, with everything else being mocks.