我是Mockito和jUnit的新手,我尝试学习正确的TDD方法。我需要一些示例,以便我可以使用mockito编写单元测试
以下是我的控制器类,它上传文件并对此文件输入执行一些操作。
@Controller
@RequestMapping("/registration")
public class RegistrationController {
@Autowired
private RegistrationService RegistrationService;
@Value("#{Properties['uploadfile.location']}")
private String uploadFileLocation;
public RegistrationController() {
}
@RequestMapping(method = RequestMethod.GET)
public String getUploadForm(Model model) {
model.addAttribute(new Registration());
return "is/Registration";
}
@RequestMapping(method = RequestMethod.POST)
public String create(Registration registration, BindingResult result,ModelMap model)
throws NumberFormatException, Exception {
File uploadedFile = uploadFile(registration);
List<Registration> userDetails = new ArrayList<Registration>();
processUploadedFile(uploadedFile,userDetails);
model.addAttribute("userDetails", userDetails);
return "registration";
}
private File uploadFile(Registration registration) {
Date dt = new Date();
SimpleDateFormat format = new SimpleDateFormat("MM_dd_yyyy_HH_mm_ss");
File uploadedFile = new File(uploadFileLocation
+ registration.getFileData().getOriginalFilename() + "."
+ format.format(dt));
registration.getFileData().transferTo(uploadedFile);
return uploadedFile;
}
private void processUploadedFile(File uploadedFile, List<Registration> userDetails)
throws NumberFormatException, Exception {
registrationService.processFile(uploadedFile, userDetails);
}
}
任何正文都可以建议一些例子如何使用mockito为此编写测试用例?
修改 我已经记下了下面的测试课程,但是如何进一步
@RunWith(MockitoJUnitRunner.class)
@ContextConfiguration(locations = { "/META-INF/spring/applicationContext.xml"})
public class BulkRegistrationControllerTest {
@InjectMocks
private RegistrationService registrationService= new RegistrationServiceImpl();
@Mock
private final ModelMap model=new ModelMap();
@InjectMocks
private ApplicationContext applicationContext;
private static MockHttpServletRequest request;
private static MockHttpServletResponse response;
private static RegistrationController registrationController;
@BeforeClass
public static void init() {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
registrationController = new RegistrationController();
}
public void testCreate()
{
final String target = "bulkRegistration";
BulkRegistration bulkRegistration=new BulkRegistration();
final BindingResult result=new BindingResult();
String nextPage=null;
nextPage = bulkRegistrationController.create(bulkRegistration, result, model);
assertEquals("Controller is not requesting the correct form",nextPage,
target);
}
}
答案 0 :(得分:14)
在您的测试中,您似乎有几件事情需要加入。有集成测试和单元测试。集成测试将测试所有连接的东西(或几乎所有东西) - 所以你使用非常接近实际的Spring配置文件,并将对象的真实示例注入到被测试的类中。这主要是我使用@ContextConfiguration
但我将它与@RunWith(SpringJUnit4ClassRunner.class)结合使用
如果您正在使用Mockito(或任何模拟框架),通常是因为您希望将您正在测试的类与其他类的实际实现隔离开来。因此,例如,不必设法让您的RegistrationService抛出NumberFormatException来测试该代码路径,而只是告诉模拟RegistrationService这样做。还有很多其他的例子,使用模拟比使用真实的类实例更方便。
所以,那个小课程结束了。以下是我将重新编写测试类的方法(附加一个示例并在此过程中进行评论)。
@RunWith(MockitoJUnitRunner.class)
public class RegistrationControllerTest {
// Create an instance of what you are going to test.
// When using the @InjectMocks annotation, you must create the instance in
// the constructor or in the field declaration.
@InjectMocks
private RegistrationController controllerUT = new RegistrationController();
// The @Mock annotation creates the mock instance of the class and
// automatically injects into the object annotated with @InjectMocks (if
// possible).
@Mock
private RegistrationService registrationService;
// This @Mock annotation simply creates a mock instance. There is nowhere to
// inject it. Depending on the particular circumstance, it may be better or
// clearer to instantiate the mock explicitly in the test itself, but we're
// doing it here for illustration. Also, I don't know what your real class
// is like, but it may be more appropriate to just instantiate a real one
// than a mock one.
@Mock
private ModelMap model;
// Same as above
@Mock
private BulkRegistration bulkRegistration;
// Same as above
@Mock
private FileData fileData;
@Before
public void setUp() {
// We want to make sure that when we call getFileData(), it returns
// something non-null, so we return the mock of fileData.
when(bulkRegistration.getFileData()).thenReturn(fileData);
}
/**
* This test very narrowly tests the correct next page. That is why there is
* so little expectation setting on the mocks. If you want to test other
* things, such as behavior when you get an exception or having the expected
* filename, you would write other tests.
*/
@Test
public void testCreate() throws Exception {
final String target = "bulkRegistration";
// Here we create a default instance of BindingResult. You don't need to
// mock everything.
BindingResult result = new BindingResult();
String nextPage = null;
// Perform the action
nextPage = controllerUT.create(bulkRegistration, result, model);
// Assert the result. This test fails, but it's for the right reason -
// you expect "bulkRegistration", but you get "registration".
assertEquals("Controller is not requesting the correct form", nextPage,
target);
}
/**
* Here is a simple example to simulate an exception being thrown by one of
* the collaborators.
*
* @throws Exception
*/
@Test(expected = NumberFormatException.class)
public void testCreateWithNumberFormatException() throws Exception {
doThrow(new NumberFormatException()).when(registrationService)
.processFile(any(File.class), anyList());
BindingResult result = new BindingResult();
// Perform the action
controllerUT.create(bulkRegistration, result, model);
}
}
答案 1 :(得分:2)
绝对有可能为Spring MVC控制器编写纯单元测试,通过模拟他们与Mockito(或JMock)的依赖关系,如上面所示的jherricks。剩下的挑战是,使用带注释的POJO控制器还有很多未经测试的 - 基本上是在注释中表达的所有内容,并且在调用控制器时由框架完成。
支持测试Spring MVC控制器正在进行中(参见spring-test-mvc project)。虽然该项目仍将进行更改,但它可以以现有形式使用。如果你对变化很敏感,那么你就不应该依赖它。无论哪种方式,我觉得值得指出,如果你想跟踪它或参与其发展。有一个夜间快照,如果您想要锁定特定版本,本月将有一个里程碑版本。
答案 2 :(得分:1)
真正的问题是:如何设置使用Spring的应用程序的测试环境?这个问题的答案并不简单,它实际上取决于您的Web应用程序的工作方式。
首先应该关注如何JUnit Java Web应用程序,然后关注如何使用Mockito。
答案 3 :(得分:1)
Mockito是一个用于模拟对象的模拟框架。当您测试依赖于某个其他对象的方法结果的方法时,这通常是可行的。例如,在测试你的create方法时,你会想要模拟uploadedFile
变量,因为你不想测试uploadFile(Registration registration)
是否正常工作(你在其他测试中测试它) ,但您有兴趣测试该方法是否正在处理上传的文件,以及它是否在模型中添加details
。要模拟上传文件,您可以:when(RegistrationController.uploadFile(anyObject()).thenReturn(new File());
但是你看到这显示了一个设计问题。您的方法uploadFile()
不应驻留在Controller中,而应驻留在其他实用程序类中。然后你可以@Mock那个实用程序类而不是控制器。
你必须记住,如果你的代码难以测试,那表明你没有尽力保持简单。
答案 4 :(得分:1)
查看上面的代码示例,我看到了一些问题:
使用Mockito的目的是模拟你的类的依赖关系。这将使您能够使用简单的JUnit测试用例。因此,无需使用@ContextConfiguration。您应该能够使用new运算符实例化正在测试的类,然后提供所需的依赖项。
您正在使用自动装配来提供注册服务。为了注入此服务的模拟实例,您需要使用Spring测试私有字段访问实用程序。
我无法从您的代码中看到RegistrationService是否是一个接口。如果不是你就会嘲笑它。
答案 5 :(得分:1)
我不熟悉Mockito(因为我使用JMock),但用模拟编写测试的一般方法是相同的。
首先,您需要一个被测试类(CUT)(RegistrationController
)的实例。那不能是模拟 - 因为你想测试它。
对于测试getUploadForm
,CUT实例不需要任何依赖项,因此您可以通过new RegistrationController
创建它。
然后你应该让测试帽看起来有点像这样
RegistrationController controller = new RegistrationController();
Model model = new Model();
String result = controller(model);
assertEquals("is/Registration", result);
assertSomeContstrainsFormodel
这很容易。
您要测试的下一个方法是create
方法。这要困难得多。
BindingResult
)可能有点复杂registrationService
和uploadFileLocation
- 这就是有趣的部分。 uploadFileLocation
只是必须在测试中设置的字段。最简单的方法是添加一个(getter和)setter来设置测试中的字段。您也可以使用org.springframework.test.util.ReflectionTestUtils
设置此字段。 - 两种方式都有优点和内容。
更有趣的是registrationService
。这应该是模拟!您需要为该类创建一个Mock,然后在CUT实例中“注入”该模拟。就像uploadFileLocation
一样,你至少有两个相同的选择。
然后,您需要定义模拟的例外:使用正确的文件和用户详细信息调用registrationService.processFile(uploadedFile, userDetails)
。 (这个例外的确定程度是Mockito的一部分 - 我没有足够的知识)。
然后你需要调用你想要在CUT上测试的方法。
BTW:如果你需要经常在Spring bean上“注入”模拟,那么你可以构建自己的util。获取一个对象的实例,扫描该对象以获得带有@Inject
注释的字段,为其创建模拟并“注入”该模拟。 (然后你只需要getter来访问模拟来定义那些知识。) - 我为JMock构建了这样一个工具,它对我帮助很大。
答案 6 :(得分:0)
替代建议:不要使用Mockito。 Spring自带了可用于模拟的测试类,您可以使用SpringJUnit4ClassRunner
。使用Spring JUnit测试运行器可以加载完整的Spring配置(通过@ContextConfiguration
)以及模拟对象。在你的情况下,你的大部分实例化代码都会消失,因为你将运行Spring,而不是模仿它的DI。
答案 7 :(得分:0)
试试这个。
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "/META-INF/spring/applicationContext.xml"}) public class BulkRegistrationControllerTest { @Mock private RegistrationService registrationService; //Controller that is being tested. @Autowired @InjectMocks private RegistrationController registrationController; @Before public void setUp() { MockitoAnnotations.initMocks(this); ... } ...