mvc - pruebas unitarias c#
cómo realizar una prueba unitaria de la aplicación principal de asp.net con la inyección de dependencia del constructor (4)
Tengo una aplicación central de asp.net que utiliza la inyección de dependencia definida en la clase startup.cs de la aplicación:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["Data:FotballConnection:DefaultConnection"]));
// Repositories
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IUserRoleRepository, UserRoleRepository>();
services.AddScoped<IRoleRepository, RoleRepository>();
services.AddScoped<ILoggingRepository, LoggingRepository>();
// Services
services.AddScoped<IMembershipService, MembershipService>();
services.AddScoped<IEncryptionService, EncryptionService>();
// new repos
services.AddScoped<IMatchService, MatchService>();
services.AddScoped<IMatchRepository, MatchRepository>();
services.AddScoped<IMatchBetRepository, MatchBetRepository>();
services.AddScoped<ITeamRepository, TeamRepository>();
services.AddScoped<IFootballAPI, FootballAPIService>();
Esto permite algo como esto:
[Route("api/[controller]")]
public class MatchController : AuthorizedController
{
private readonly IMatchService _matchService;
private readonly IMatchRepository _matchRepository;
private readonly IMatchBetRepository _matchBetRepository;
private readonly IUserRepository _userRepository;
private readonly ILoggingRepository _loggingRepository;
public MatchController(IMatchService matchService, IMatchRepository matchRepository, IMatchBetRepository matchBetRepository, ILoggingRepository loggingRepository, IUserRepository userRepository)
{
_matchService = matchService;
_matchRepository = matchRepository;
_matchBetRepository = matchBetRepository;
_userRepository = userRepository;
_loggingRepository = loggingRepository;
}
Esto es muy limpio. Pero se convierte en un problema cuando quiero hacer una prueba unitaria. Debido a que mi biblioteca de prueba no tiene un archivo startup.cs donde instale la inyección de dependencias. Así que una clase con estas interfaces como parámetros será nula.
namespace TestLibrary
{
public class FootballAPIService
{
private readonly IMatchRepository _matchRepository;
private readonly ITeamRepository _teamRepository;
public FootballAPIService(IMatchRepository matchRepository, ITeamRepository teamRepository)
{
_matchRepository = matchRepository;
_teamRepository = teamRepository;
En el código anterior, en la biblioteca de prueba, _matchRepository y _teamRepository , solo serán nulos . :(
¿Puedo hacer algo como ConfigureServices, donde defino la inyección de dependencias en mi proyecto de biblioteca de prueba?
¿Por qué querrías inyectarlos en una clase de prueba? Por lo general, probaría el MatchController, por ejemplo, usando una herramienta como RhinoMocks para crear stubs o RhinoMocks . Aquí hay un ejemplo que usa eso y MSTest, desde donde puedes extrapolar:
[TestClass]
public class MatchControllerTests
{
private readonly MatchController _sut;
private readonly IMatchService _matchService;
public MatchControllerTests()
{
_matchService = MockRepository.GenerateMock<IMatchService>();
_sut = new ProductController(_matchService);
}
[TestMethod]
public void DoSomething_WithCertainParameters_ShouldDoSomething()
{
_matchService
.Expect(x => x.GetMatches(Arg<string>.Is.Anything))
.Return(new []{new Match()});
_sut.DoSomething();
_matchService.AssertWasCalled(x => x.GetMatches(Arg<string>.Is.Anything);
}
Aunque la respuesta de @Kritner es correcta, prefiero lo siguiente para la integridad del código y una mejor experiencia de DI:
[TestClass]
public class MatchRepositoryTests
{
private readonly IMatchRepository matchRepository;
public MatchRepositoryTests()
{
var services = new ServiceCollection();
services.AddTransient<IMatchRepository, MatchRepository>();
var serviceProvider = services.BuildServiceProvider();
matchRepository = serviceProvider.GetService<IMatchRepository>();
}
}
De manera simple, escribí una clase de ayudante de resolución de dependencias genérica y luego construí el IWebHost en mi clase de prueba de unidad.
Resolución de dependencia genérica
public class DependencyResolverHelpercs
{
private readonly IWebHost _webHost;
/// <inheritdoc />
public DependencyResolverHelpercs(IWebHost WebHost) => _webHost = WebHost;
public T GetService<T>()
{
using (var serviceScope = _webHost.Services.CreateScope())
{
var services = serviceScope.ServiceProvider;
try
{
var scopedService = services.GetRequiredService<T>();
return scopedService;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
};
}
}
}
Proyecto de prueba unitaria
[TestFixture]
public class DependencyResolverTests
{
private DependencyResolverHelpercs _serviceProvider;
public DependencyResolverTests()
{
var webHost = WebHost.CreateDefaultBuilder()
.UseStartup<Startup>()
.Build();
_serviceProvider = new DependencyResolverHelpercs(webHost);
}
[Test]
public void Service_Should_Get_Resolved()
{
//Act
var YourService = _serviceProvider.GetService<IYourService>();
//Assert
Assert.IsNotNull(YourService);
}
}
Sus controladores en .net core tienen en mente la inyección de dependencia desde el principio, pero esto no significa que deba usar un contenedor de inyección de dependencia.
Dada una clase más simple como:
public class MyController : Controller
{
private readonly IMyInterface _myInterface;
public MyController(IMyInterface myInterface)
{
_myInterface = myInterface;
}
public JsonResult Get()
{
return Json(_myInterface.Get());
}
}
public interface IMyInterface
{
IEnumerable<MyObject> Get();
}
public class MyClass : IMyInterface
{
public IEnumerable<MyObject> Get()
{
// implementation
}
}
Entonces, en su aplicación, está utilizando el contenedor de inyección de dependencias en su startup.cs
, que no hace más que proporcionar una concreción de MyClass
para usar cuando se encuentra IMyInterface
. Sin embargo, esto no significa que sea la única forma de obtener instancias de MyController
.
En un escenario de prueba unitaria , puede (y debe) proporcionar su propia implementación (o simulacro / talón / falso) de IMyInterface
como tal:
public class MyTestClass : IMyInterface
{
public IEnumerable<MyObject> Get()
{
List<MyObject> list = new List<MyObject>();
// populate list
return list;
}
}
y en tu prueba:
[TestClass]
public class MyControllerTests
{
MyController _systemUnderTest;
IMyInterface _myInterface;
[TestInitialize]
public void Setup()
{
_myInterface = new MyTestClass();
_systemUnderTest = new MyController(_myInterface);
}
}
Por lo tanto, para el alcance de las pruebas unitarias de MyController
, la implementación real de IMyInterface
no importa (y no debería importar), solo la interfaz en sí importa. Hemos proporcionado una implementación "falsa" de IMyInterface
través de MyTestClass
, pero también puede hacerlo con un simulacro a través de Moq
o RhinoMocks
.
En pocas palabras, no necesita realmente el contenedor de inyección de dependencias para realizar sus pruebas, solo una implementación / simulacro / falso independiente y controlable de sus clases de clases probadas.