← Назад к вопросам

Как написать unit-тест проверяющий что метод был вызван?

1.3 Junior🔥 111 комментариев
#Другое

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Unit-тесты для проверки вызова методов в Unity/C#

При написании unit-тестов в Unity (обычно с использованием NUnit и фреймворков для мокинга вроде NSubstitute или Moq) проверка вызова метода — одна из фундаментальных задач. Вот как это правильно организовать.

Основные подходы

В зависимости от архитектуры вашего кода и используемых инструментов, есть несколько способов проверить вызов метода:

  1. Использование фреймворков для мокинга (предпочтительный способ)
  2. Ручные моки-заглушки (для простых случаев)
  3. Проверка побочных эффектов (косвенная проверка)

Пример с NSubstitute (рекомендуемый фреймворк)

Установите NSubstitute через Package Manager или добавьте в *.csproj. Далее создайте тест:

using NSubstitute;
using NUnit.Framework;

public class DamageServiceTests
{
    // Интерфейс или абстрактный класс, который мы подменяем
    public interface IHealthSystem
    {
        void ApplyDamage(int amount);
    }

    // Класс, который тестируем
    public class DamageService
    {
        private IHealthSystem _healthSystem;
        
        public DamageService(IHealthSystem healthSystem)
        {
            _healthSystem = healthSystem;
        }
        
        public void DealCriticalDamage()
        {
            // Логика расчета урона...
            int calculatedDamage = 100;
            _healthSystem.ApplyDamage(calculatedDamage);
        }
    }

    [Test]
    public void DealCriticalDamage_Should_Call_ApplyDamage_WithCorrectValue()
    {
        // Arrange
        var mockHealthSystem = Substitute.For<IHealthSystem>();
        var damageService = new DamageService(mockHealthSystem);
        
        // Act
        damageService.DealCriticalDamage();
        
        // Assert
        // Проверяем, что метод был вызван ровно один раз с аргументом 100
        mockHealthSystem.Received(1).ApplyDamage(100);
        
        // Альтернативные проверки:
        // mockHealthSystem.Received().ApplyDamage(Arg.Any<int>()); // Любое значение int
        // mockHealthSystem.Received().ApplyDamage(Arg.Is<int>(x => x > 50)); // С условием
        // mockHealthSystem.DidNotReceive().ApplyDamage(Arg.Any<int>()); // Отрицательная проверка
    }
}

Пример с ручной заглушкой (Manual Mock)

Если не хотите использовать сторонние библиотеки:

using NUnit.Framework;

public class ManualMockTests
{
    public interface ISaveSystem
    {
        void SaveGame(string data);
    }
    
    public class GameManager
    {
        private ISaveSystem _saveSystem;
        
        public GameManager(ISaveSystem saveSystem)
        {
            _saveSystem = saveSystem;
        }
        
        public void CompleteLevel()
        {
            // Какая-то логика...
            _saveSystem.SaveGame("LevelCompleted");
        }
    }
    
    // Ручной мок-класс
    public class MockSaveSystem : ISaveSystem
    {
        public int CallCount { get; private set; }
        public string LastCallArgument { get; private set; }
        
        public void SaveGame(string data)
        {
            CallCount++;
            LastCallArgument = data;
        }
    }

    [Test]
    public void CompleteLevel_Should_Call_SaveGame_Once()
    {
        // Arrange
        var mockSaveSystem = new MockSaveSystem();
        var gameManager = new GameManager(mockSaveSystem);
        
        // Act
        gameManager.CompleteLevel();
        
        // Assert
        Assert.AreEqual(1, mockSaveSystem.CallCount, "Метод должен быть вызван ровно 1 раз");
        Assert.AreEqual("LevelCompleted", mockSaveSystem.LastCallArgument);
    }
}

Ключевые принципы для качественных тестов

  • Инверсия зависимостей — тестируемый класс должен зависеть от абстракций (интерфейсов), а не от конкретных реализаций
  • Изоляция тестов — каждый тест должен быть независим от других и от внешних состояний
  • Четкие названия — имена тестов должны точно отражать проверяемое поведение (паттерн MethodName_Scenario_ExpectedResult)
  • AAA-паттерн — четкое разделение на этапы Arrange-Act-Assert

Особенности для MonoBehaviour в Unity

Для MonoBehaviour-классов подход аналогичен, но нужно учитывать:

using UnityEngine;
using NSubstitute;
using NUnit.Framework;

public class PlayerControllerTests
{
    public class PlayerController : MonoBehaviour
    {
        private IAudioService _audioService;
        
        public void Initialize(IAudioService audioService)
        {
            _audioService = audioService;
        }
        
        public void PlayJumpSound()
        {
            _audioService.PlaySound("jump");
        }
    }
    
    [Test]
    public void PlayJumpSound_Should_Trigger_AudioService()
    {
        // Arrange
        var mockAudioService = Substitute.For<IAudioService>();
        var player = new GameObject().AddComponent<PlayerController>();
        player.Initialize(mockAudioService);
        
        // Act
        player.PlayJumpSound();
        
        // Assert
        mockAudioService.Received(1).PlaySound("jump");
    }
}

Распространенные ошибки и решения

  • Ошибка: Тестирование внутренних приватных методов.
    Решение: Тестируйте только публичный API. Если нужно проверить приватный метод — пересмотрите дизайн класса.

  • Ошибка: Проверка вызова метода Unity (например, Transform.Translate).
    Решение: Создайте абстракцию над Unity-компонентами через интерфейсы.

  • Ошибка: Использование реальных зависимостей в тестах.
    Решение: Всегда подменяйте внешние зависимости моками.

Использование мокинга для проверки вызовов методов делает тесты быстрыми, стабильными и изолированными, что является краеугольным камнем качественной unit-тестируемости кода в Unity-проектах.