Как я могу использовать Moq здесь? - PullRequest
1 голос
/ 16 апреля 2019

Мой проект WEB API использует репозиторий Generic, который реализует такой интерфейс:

public interface IGenericEFRepository<TEntity> where TEntity : class
{
    Task<IEnumerable<TEntity>> Get();
    Task<TEntity> Get(int id);
}

public class GenericEFRepository<TEntity> : IGenericEFRepository<TEntity>
    where TEntity : class
{
    private SqlDbContext _db;
    public GenericEFRepository(SqlDbContext db)
    {
        _db = db;
    }

    public async Task<IEnumerable<TEntity>> Get()
    {
        return await Task.FromResult(_db.Set<TEntity>());
    }

    public async Task<TEntity> Get(int id)
    {
        var entity = await Task.FromResult(_db.Set<TEntity>().Find(new object[] { id }));

        if (entity != null && includeRelatedEntities)
        {
            //Some Code
        }
        return entity;
    }
}

Что ж, теперь я хочу протестировать этот сервис.для этого я использовал следующий код:

public class CustomerControllerTest
{
    CustomerController _controller;
    ICustomerProvider _provider;
    ICustomerInquiryMockRepository _repo;

    public CustomerControllerTest()
    {
        _repo = new CustomerInquiryMockRepository();
        _provider = new CustomerProvider(_repo);
        _controller = new CustomerController(_provider);
    }

     [Fact]
    public async Task Get_WhenCalled_ReturnsOkResult()
    {
        // Act
        var okResult = await _controller.Get();

        // Assert
        Assert.IsType<OkObjectResult>(okResult);
    }

    [Fact]
    public async Task GetById_UnknownCustomerIdPassed_ReturnsNotFoundResult()
    {
        // Act
        var notFoundResult = await _controller.Get(4);

        // Assert
        Assert.IsType<NotFoundResult>(notFoundResult);
    }
}

Что мои тесты работают нормально, создавая фальшивый неуниверсальный сервис вручную с фиктивными данными (в памяти), как показано ниже, вместо использования моего реального универсальногоинтерфейс и его реализация, которая использует мою базу данных в качестве источника данных:

public interface ICustomerInquiryMockRepository
{
    Task<IEnumerable<CustomerDTO>> GetCustomers();
    Task<CustomerDTO> GetCustomer(int customerId);
}

И это реализация:

public class CustomerInquiryMockRepository : ICustomerInquiryMockRepository
{       
    public async Task<IEnumerable<CustomerDTO>> GetCustomers()
    {
        return await Task.FromResult(MockData.Current.Customers);
    }

    public async Task<CustomerDTO> GetCustomer(int CustomerId)
    {
        var Customer = await Task.FromResult(MockData.Current.Customers.FirstOrDefault(p => p.CustomerID.Equals(CustomerId)));

        if (includeTransactions && Customer != null)
        {
            Customer.Transactions = MockData.Current.Transactions.Where(b => b.CustomerId.Equals(CustomerId)).ToList();
        }

        return Customer;
    }
}

А MockData.Current.Customers - это просто подделка (In-Memory)Список клиентов.Короче говоря, вышеприведенные тесты работают нормально, однако я чувствую, что многократно повторял сам, и поэтому я решил использовать библиотеку Moq вместо создания фальшивого сервиса вручную.Для этой цели я использовал Moq следующим образом:

public class CustomerControllerTest
{
    CustomerController _controller;
    ICustomerProvider _provider;
    //ICustomerInquiryMockRepository _repo;
    Mock<ICustomerInquiryMockRepository> mockUserRepo;


    public CustomerControllerTest()
    {
        mockUserRepo = new Mock<ICustomerInquiryMockRepository>();
        //_repo = new CustomerInquiryMockRepository();
        _provider = new CustomerProvider(mockUserRepo.Object);
        _controller = new CustomerController(_provider);
    }

    [Fact]
    public async Task Get_WhenCalled_ReturnsOkResult()
    {
        mockUserRepo.Setup(m => m.GetCustomers())
            .Returns(Task.FromResult(MockData.Current.Customers.AsEnumerable()));
        // Act
        var okResult = await _controller.Get();

        // Assert
        Assert.IsType<OkObjectResult>(okResult);
    }


    [Fact]
    public async Task GetById_UnknownCustomerIdPassed_ReturnsNotFoundResult()
    {
        //Arrange
        I don't know how can I use Moq here and in the other parts of my tests

        // Act
        var notFoundResult = await _controller.Get(4);

        // Assert
        Assert.IsType<NotFoundResult>(notFoundResult);
    }

Теперь мой вопрос: Mock работает нормально, когда я использую его для Mocking метода GetCustomers, потому что я просто вставляю код из GetCustomers метод в CustomerInquiryMockRepository в методе Returns объекта Mock.Однако я не имею ни малейшего представления, как я могу использовать Mock для других своих методов в этом репозитории.Должен ли я заменить что-либо, что у меня есть в методе Return?

1 Ответ

2 голосов
/ 16 апреля 2019

Вы можете смоделировать свой репозиторий следующим образом:

var mockUserRepo = new Mock<ICustomerInquiryMockRepository>();
mockUserRepo.Setup(x => x.GetCustomers())
            .Returns(Task.FromResult(MockData.Current.Customers.AsEnumerable());
mockUserRepo.Setup(x => x.GetCustomer(It.IsAny<int>()))
            .Returns(res => Task.FromResult(MockData.Current.Customers.ElementAt(res));

Если вы хотите смоделировать конкретные значения для GetCustomer, вы можете сделать:

mockUserRepo.Setup(x => x.GetCustomer(It.Is<int>(y => y == 4)))
            .Returns(res => Task.FromResult(/* error value here */));

Я думаю, чтоЗдесь ключом является использование It.Is или It.IsAny в зависимости от того, как вы хотите смоделировать объект.Как правило, вы также хотите макетировать интерфейсы, которые используются в производственном коде, вместо того, чтобы производственный код зависел от чего-то с Mock или Test в имени.Я бы рекомендовал не принимать зависимость производственного кода от чего-то с именем ICustomerInquiryMockRepository, если это действительно то, что вы делаете, а не просто часть MCVE, которую вы предоставили.

Тесты обычно используют макеты для проверкиРабочий процесс приложения находится на высоком уровне, поэтому вы обычно хотели бы смоделировать уровень своих услуг, вызвать контроллер и убедиться, что службы были вызваны так, как ожидалось.Например:

// Production class sample
class ProductionController
{
  public ProductionController(IService1 service1, IService2 service2) { }

  public void ControllerMethod()
  {
    var service1Result = service1.Method();
    service2.Method(service1Result);
  }
}

// Test sample
// arrange
var expectedResult = new Service1Result();
var service1 = Mock.Of<IService1>(x => x.Method() == expectedResult);
var service2 = Mock.Of<IService2>(x => x.Method(It.Is<Service1Result>(y => y == expectedResult)));
var controller = new ProductionController(service1, service2);

// act
controller.ControllerMethod();

// assert
Mock.Get(service1).Verify(x => x.Method(), Times.Once);
Mock.Get(service2).Verify(x => x.Method(expectedResult), Times.Once);

Как видно из примера, вы не проверяете бизнес-логику какой-либо из служб, вы просто проверяете, что методы были вызваны с ожидаемыми данными.Тест построен на проверке методов, которые называются , а не какой-либо конкретной логики ветвления.


Кроме того, независимо от вашего вопроса, Moq также имеет классный синтаксис, который вы можете использовать для простогонастройки макета:

var repo = Mock.Of<ICustomerInquiryMockRepository>(x => 
    x.GetCustomers() == Task.FromResult(MockData.Current.Customers.AsEnumerable()));

Вы можете использовать Mock.Get(repo), если вам нужно выполнить дополнительную настройку в хранилище.Это определенно стоит проверить, мне гораздо приятнее читать.

...