Unit tests with Moq in .NET
Bài đăng này đã không được cập nhật trong 8 năm
Hôm nay mình sẽ nói về viết Unit test với Mock Object thông qua thư viện Moq trong .Net
Mock Object là gì
Trong hướng đối tượng, mock object là đối tượng giả lập để mô tả, giả hành vi của đối tượng thật. Lập trình viên thường tạo ra các mock object để test các hành vi và chức năng của đối tượng khác.
Đến đây là các bạn đủ hiểu mock object dùng để làm gì rồi đúng không. Mock object là một cách tuyệt vời để mô phỏng các đối tượng mà chúng ta cần làm việc. Ngoài ra chúng ta còn dùng nó để xuyên qua các trường hợp khó reproduce.Đấy là khi cần tạo các case liên quan tới các case exception trong db, trong network, trong các tầng service mức thấp.v.v.
Và một điều mà đấy cũng là lí do mình viết bài hôm nay đấy là việc tạo Mock Object sẽ không gây bất cứ ảnh hưởng gì cho database của bạn. Nếu bạn đã từng làm việc với unit test chắc chắn các bạn sẽ biết đến 1 db đầy là db test và nếu sự dụng db test thì rất nhiều tính huống xảy ra như việc dữ liệu db test không đúng, bị conflict với các version target và cho dù bạn remove bằng tay hoặc bằng code thì điều đấy cũng sẽ rất rối loạn và tạo ra những đoạn code rất nhàm chán trong unit test của bạn. Và nếu ai maintain đống unit test này thì ngốn một đống thời gian để hiểu bạn đang thao tác với data như thế nào.
Sử dụng MockObject thông qua Moq trong .Net
Chúng ta sẽ giới thiệu về Moq
Moq là một mã nguồn mở được xây dựng dựa trên .Net 3.5, nó cung cấp các kiểu asert để kiểm tra spec của unit test. Để sử dụng mock trong project chúng ta làm như sau. Đầu tiên là setup mock object.
var _mock = new Mock<IObject>();
Ở đây tất nhiên mọi object chúng ta đều phải sử dụng interface của nó nhé. Sau đó chúng ta thiết lập các giá trị cũng như kết quả của các hành vi thuộc đối tượng này trả về spec như thế nào trong từng case dữ liệu đầu vào ( tín hiện vào). Mình dùng tín hiệu đầu vào để các bạn hiểu là chúng ta áp dụng được đối với mọi đối tượng từ view, controller, model thuộc mọi lớp kiến trúc dự án nhé. ( Tất nhiên thằng nào ko code với Interface thì miễn nhá).
Ví dụ: Mình có IUserServiceClient trong này có phương thức GetUsers(). Và mình sẽ setup để khi gọi GetUsers của UserServiceClient thì nó sẽ trả đúng user mà mình mong muốn
1 Khởi tạo dữ liệu List<AccountModel> users = new List<AccountModel>() { new AccountModel() { ID = 1 }, new AccountModel() { ID = 2 } };
2 Setup Mock
var mockService = new Mock<IUserServiceClient>(); mockService.Setup(m => m.GetUsers()).Returns(users);
Sau đó mình khởi tạo 1 controller sử dụng thằng UserServiceClient Mock này UsersController controller = new UsersController(mockService.Object, mockLogger.Object);
UsersController khi function get users nó sẽ trả lại list user chính là list users chúng ta đã tạo.
var result = controller.Index(); var model = (List<...ViewModels.Users>)result.ViewData.Model; Assert.IsTrue(model.Count == 2); Assert.AreEqual(model[0].ID, 1); Assert.AreEqual(model[1].ID, 2);
Ở đây các bạn sẽ thấy chúng ta muốn test kiểu tra function get list user của thằng UserController. Và chúng ta sẽ tạo mock object cho thằng UserServiceClient với dữ liệu user được tạo ra. Khi đấy chúng ta sẽ đi test các hành vi của UserController. Và không hề ảnh hưởng và cần đến database test. Mọi thứ đề rất trong sáng và 1 tester hoặc dev đều có thể nhìn vào unit test của bạn và biết bạn đang thao tác dữ liệu gì, spec là gì, test cho đối tượng nào và thiết lập "dữ liệu" giả cho tầng nào.
Hy vọng bài viết sẽ thú vị với mọi người.
Mình để lại đoạn code tổng bên dưới để mọi người có thể xem tổng quan. Thân
//using System;
//using System.Web;
//using System.Web.Mvc;
//using System.Web.Routing;
//using Microsoft.VisualStudio.TestTools.UnitTesting;
//using ___.Web.Core.ViewModels;
//using ___.Web.Core.Controllers;
//using ___.Web.Core;
//using System.Collections.Generic;
//using ___.App.DomainModels;
//using Moq;
//using log4net;
//using ___.Web.WebApiServiceClient;
//namespace ___.Web.Test
//{
// [TestClass]
// public class Web_UserControllerTest
// {
// [TestMethod]
// public void Web_Index_Get_AsksForIndexView()
// {
// // Arrange
// var controller = getUnitUnderTest();
// // Act
// ViewResult result = controller.Index();
// // Assert
// Assert.AreEqual("Index", result.ViewName);
// }
// [TestMethod]
// public void Web_Index_Get_RetrievesAllUsersFromRepository()
// {
// // Arrange
// var controller = getUnitUnderTest();
// // Act
// var result = controller.Index();
// // Assert
// var model = (List<___.Web.Core.ViewModels.Users>)result.ViewData.Model;
// Assert.IsTrue(model.Count == 2);
// Assert.AreEqual(model[0].ID, 1);
// Assert.AreEqual(model[1].ID, 2);
// }
// private static UsersController getUnitUnderTest()
// {
// List<AccountModel> users = new List<AccountModel>()
// {
// new AccountModel() { ID = 1 },
// new AccountModel() { ID = 2 }
// };
// var mockService = new Mock<IUserServiceClient>();
// mockService.Setup(m => m.GetUsers()).Returns(users);
// var mockLogger = new Mock<ILog>();
// UsersController controller = new UsersController(mockService.Object, mockLogger.Object);
// return controller;
// }
// }
//}
All rights reserved