I'm adding some unit tests for my ASP.NET Core Web API, and I'm wondering whether to unit test the controllers directly or through an HTTP client. Directly would look roughly like this:
[TestMethod]
public async Task GetGroups_Succeeds()
{
var controller = new GroupsController(
_groupsLoggerMock.Object,
_uowRunnerMock.Object,
_repoFactoryMock.Object
);
var groups = await controller.GetGroups();
Assert.IsNotNull(groups);
}
... whereas through an HTTP client would look roughly like this:
[TestMethod]
public void GetGroups_Succeeds()
{
HttpClient.Execute();
dynamic obj = JsonConvert.DeserializeObject<dynamic>(HttpClient.ResponseContent);
Assert.AreEqual(200, HttpClient.ResponseStatusCode);
Assert.AreEqual("OK", HttpClient.ResponseStatusMsg);
string groupid = obj[0].id;
string name = obj[0].name;
string usercount = obj[0].userCount;
string participantsjson = obj[0].participantsJson;
Assert.IsNotNull(name);
Assert.IsNotNull(usercount);
Assert.IsNotNull(participantsjson);
}
Searching online, it looks like both ways of testing an API seem to be used, but I'm wondering what the best practice is. The second method seems a bit better because it naively tests the actual JSON response from the Web API without knowing the actual response object type, but it's more difficult to inject mock repositories this way - the tests would have to connect to a separate local Web API server that itself was somehow configured to use mock objects... I think?
Edit: TL;DR
The conclusion you should do both because each test serves a different purpose.
Answer:
This is a good question, one I often ask myself.
First, you must look at the purpose of a unit test and the purpose of an integration test.
Unit Test :
Unit tests involve testing a part of an app in isolation from its
infrastructure and dependencies. When unit testing controller logic,
only the contents of a single action are tested, not the behaviour of
its dependencies or of the framework itself.
Things like filters, routing, and model binding will not work.
Integration Test :
Integration tests ensure that an app's components function correctly
at a level that includes the app's supporting infrastructures, such as
the database, file system, and network. ASP.NET Core supports
integration tests using a unit test framework with a test web host and
an in-memory test server.
Things like filters, routing, and model binding will work.
“Best practice” should be thought of as “Has value and makes sense”.
You should ask yourself Is there any value in writing the test, or am I just creating this test for the sake of writing a test?
Let's say your GetGroups() method looks like this.
[HttpGet]
[Authorize]
public async Task<ActionResult<Group>> GetGroups()
{
var groups = await _repository.ListAllAsync();
return Ok(groups);
}
There is no value in writing a unit test for it! because what you are doing is testing a mocked implementation of _repository! So what is the point of that?!
The method has no logic and the repository is only going to be exactly what you mocked it to be, nothing in the method suggests otherwise.
The Repository will have its own set of separate unit tests where you will cover the implementation of the repository methods.
Now let's say your GetGroups() method is more than just a wrapper for the _repository and has some logic in it.
[HttpGet]
[Authorize]
public async Task<ActionResult<Group>> GetGroups()
{
List<Group> groups;
if (HttpContext.User.IsInRole("Admin"))
groups = await _repository.FindByExpressionAsync(g => g.IsAdminGroup == true);
else
groups = await _repository.FindByExpressionAsync(g => g.IsAdminGroup == false);
//maybe some other logic that could determine a response with a different outcome...
return Ok(groups);
}
Now there is value in writing a unit test for the GetGroups() method because the outcome could change depending on the mocked HttpContext.User value.
Attributes like [Authorize] or [ServiceFilter(….)] will not be triggered in a unit test.
.
Writing integration tests is almost always worth it because you want to test what the process will do when it forms part of an actual application/system/process.
Ask yourself, is this being used by the application/system?
If yes, write an integration test because the outcome depends on a combination of circumstances and criteria.
Now even if your GetGroups() method is just a wrapper like in the first implementation, the _repository will point to an actual datastore, nothing is mocked!
So now, not only does the test cover the fact that the datastore has data (or not), it also relies on an actual connection being made, HttpContext being set up properly and whether serialisation of the information works as expected.
Things like filters, routing, and model binding will also work.
So if you had an attribute on your GetGroups() method, for example [Authorize] or [ServiceFilter(….)], it will be triggered as expected.
I use xUnit for testing so for a unit test on a controller I use this.
Controller Unit Test:
public class MyEntityControllerShould
{
private MyEntityController InitializeController(AppDbContext appDbContext)
{
var _controller = new MyEntityController (null, new MyEntityRepository(appDbContext));
var httpContext = new DefaultHttpContext();
var context = new ControllerContext(new ActionContext(httpContext, new RouteData(), new ActionDescriptor()));
_controller.ControllerContext = context;
return _controller;
}
[Fact]
public async Task Get_All_MyEntity_Records()
{
// Arrange
var _AppDbContext = AppDbContextMocker.GetAppDbContext(nameof(Get_All_MeetUp_Records));
var _controller = InitializeController(_AppDbContext);
//Act
var all = await _controller.GetAllValidEntities();
//Assert
Assert.True(all.Value.Count() > 0);
//clean up otherwise the other test will complain about key tracking.
await _AppDbContext.DisposeAsync();
}
}
The Context mocker used for unit testing.
public class AppDbContextMocker
{
/// <summary>
/// Get an In memory version of the app db context with some seeded data
/// </summary>
/// <param name="dbName"></param>
/// <returns></returns>
public static AppDbContext GetAppDbContext(string dbName)
{
//set up the options to use for this dbcontext
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase(dbName)
.Options;
var dbContext = new AppDbContext(options);
dbContext.SeedAppDbContext();
return dbContext;
}
}
The Seed extension.
public static class AppDbContextExtensions
{
public static void SeedAppDbContext(this AppDbContext appDbContext)
{
var myEnt = new MyEntity()
{
Id = 1,
SomeValue = "ABCD",
}
appDbContext.MyENtities.Add(myEnt);
//add more seed records etc....
appDbContext.SaveChanges();
//detach everything
foreach (var entity in appDbContext.ChangeTracker.Entries())
{
entity.State = EntityState.Detached;
}
}
}
and for Integration Testing: (this is some code from a tutorial, but I can't remember where I saw it, either youtube or Pluralsight)
setup for the TestFixture
public class TestFixture<TStatup> : IDisposable
{
/// <summary>
/// Get the application project path where the startup assembly lives
/// </summary>
string GetProjectPath(string projectRelativePath, Assembly startupAssembly)
{
var projectName = startupAssembly.GetName().Name;
var applicationBaseBath = AppContext.BaseDirectory;
var directoryInfo = new DirectoryInfo(applicationBaseBath);
do
{
directoryInfo = directoryInfo.Parent;
var projectDirectoryInfo = new DirectoryInfo(Path.Combine(directoryInfo.FullName, projectRelativePath));
if (projectDirectoryInfo.Exists)
{
if (new FileInfo(Path.Combine(projectDirectoryInfo.FullName, projectName, $"{projectName}.csproj")).Exists)
return Path.Combine(projectDirectoryInfo.FullName, projectName);
}
} while (directoryInfo.Parent != null);
throw new Exception($"Project root could not be located using application root {applicationBaseBath}");
}
/// <summary>
/// The temporary test server that will be used to host the controllers
/// </summary>
private TestServer _server;
/// <summary>
/// The client used to send information to the service host server
/// </summary>
public HttpClient HttpClient { get; }
public TestFixture() : this(Path.Combine(""))
{ }
protected TestFixture(string relativeTargetProjectParentDirectory)
{
var startupAssembly = typeof(TStatup).GetTypeInfo().Assembly;
var contentRoot = GetProjectPath(relativeTargetProjectParentDirectory, startupAssembly);
var configurationBuilder = new ConfigurationBuilder()
.SetBasePath(contentRoot)
.AddJsonFile("appsettings.json")
.AddJsonFile("appsettings.Development.json");
var webHostBuilder = new WebHostBuilder()
.UseContentRoot(contentRoot)
.ConfigureServices(InitializeServices)
.UseConfiguration(configurationBuilder.Build())
.UseEnvironment("Development")
.UseStartup(typeof(TStatup));
//create test instance of the server
_server = new TestServer(webHostBuilder);
//configure client
HttpClient = _server.CreateClient();
HttpClient.BaseAddress = new Uri("http://localhost:5005");
HttpClient.DefaultRequestHeaders.Accept.Clear();
HttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
/// <summary>
/// Initialize the services so that it matches the services used in the main API project
/// </summary>
protected virtual void InitializeServices(IServiceCollection services)
{
var startupAsembly = typeof(TStatup).GetTypeInfo().Assembly;
var manager = new ApplicationPartManager
{
ApplicationParts = {
new AssemblyPart(startupAsembly)
},
FeatureProviders = {
new ControllerFeatureProvider()
}
};
services.AddSingleton(manager);
}
/// <summary>
/// Dispose the Client and the Server
/// </summary>
public void Dispose()
{
HttpClient.Dispose();
_server.Dispose();
_ctx.Dispose();
}
AppDbContext _ctx = null;
public void SeedDataToContext()
{
if (_ctx == null)
{
_ctx = _server.Services.GetService<AppDbContext>();
if (_ctx != null)
_ctx.SeedAppDbContext();
}
}
}
and use it like this in the integration test.
public class MyEntityControllerShould : IClassFixture<TestFixture<MyEntityApp.Api.Startup>>
{
private HttpClient _HttpClient;
private const string _BaseRequestUri = "/api/myentities";
public MyEntityControllerShould(TestFixture<MyEntityApp.Api.Startup> fixture)
{
_HttpClient = fixture.HttpClient;
fixture.SeedDataToContext();
}
[Fact]
public async Task Get_GetAllValidEntities()
{
//arrange
var request = _BaseRequestUri;
//act
var response = await _HttpClient.GetAsync(request);
//assert
response.EnsureSuccessStatusCode(); //if exception is not thrown all is good
//convert the response content to expected result and test response
var result = await ContentHelper.ContentTo<IEnumerable<MyEntities>>(response.Content);
Assert.NotNull(result);
}
}
Added Edit:
In conclusion, you should do both, because each test serves a different purpose.
Looking at the other answers you will see that the consensus is to do both.
TL;DR
Is it best practice to test [...] directly or through an HTTP client?
Not "or" but "and". If you serious about best practices of testing - you need both tests.
First test is a unit test. But the second one is an integration test.
There is a common consensus (test pyramid) that you need more unit tests comparing to the number of integration tests. But you need both.
There are many reasons why you should prefer unit tests over integration tests, most of them boil down to the fact that unit test are small (in all senses) and integration tests - aren't. But the main 4 are:
Locality
When your unit test fails, usually, just from it's name you can figure out the place where the bug is. When integration test becomes red, you can't say right away where is the issue. Maybe it's in the controller.GetGroups or it's in the HttpClient, or there is some issue with the network.
Also, when you introduce a bug in your code it's quite possible that only one of unit tests will become red, while with integration tests there are more chances that more than one of them will fail.
Stability
With a small project which you can test on you local box you probably won't notice it. But on a big project with distributed infrastructure you will see blinking tests all the time. And that will become a problem. At some point you can find yourself not trusting test results anymore.
Speed
With a small project with a small number of tests you won't notice it. But on a bit project it will become a problem. (Network delays, IO delays, initialization, cleanup, etc., etc.)
Simplicity
You've noticed it yourself.
But that not always true. If you code is poorly structured, then it's easier to write integration tests. And that's one more reason why you should prefer unit tests. In some way they force you to write more modular code (and I'm not taking about Dependency Injection).
But also keep in mind that best practices are almost always about big projects. If your project is small, and will stay small, there are a big chance that you'll be better off with strictly opposite decisions.
Write more tests. (Again, that means - both). Become better at writing tests. Delete them latter.
Practice makes perfect.
If we limit the scope of discussion to Controller vs HttpClient testing comparison, I would say that it is better to use HttpClient. Because if you write tests for your controllers, you're already writing integration tests already and there is almost no point to write "weaker" integration tests while you can write stronger ones that is more realistic and also superset of the weaker ones.
For example, you can see from your own example that both of your test are testing exactly the same functionality. The different is that the latter one cover more area of testing -- JSON response, or can be something else like HTTP header you want to test. If you write the latter test, you don't need the first test at all.
I understand the pain of how to inject mocked dependencies. This requires more effort comparing to testing controller directly. However, .NET Core already provides a good set of tools to help you on that. You can setup the test host inside the test itself, configure it and get HttpClient from it. Then you can use that HttpClient for your testing purpose.
The other concern is that it is quite a tedious task to craft HttpClient's request for each test. Anyway, Refit can help you a lot on this. Refit's declarative syntax is quite easy to understand (and maintain eventually). While I would also recommend Refit for all remote API calls, it is also suitable for ASP.NET Core integration testing.
Combining all solutions available, I don't see why you should limit to controller test while you can go for more "real" integration test with only some little more effort.
I never have liked mocking in that as applications mature the effort spent on mocking can make for a ton of effort.
I like exercising endpoints by direct Http calls. Today there are fantastic tools like Cypress which allow the client requests to be intercepted and altered. The power of this feature along with easy Browser based GUI interaction blurs traditional test definitions because one test in Cypress can be all of these types Unit, Functional, Integration and E2E.
If an endpoint is bullet proof then error injection becomes impossible from outside. But even errors from within are easy to simulate. Run the same Cypress tests with Db down. Or inject intermittent network issue simulation from Cypress. This is mocking issues externally which is closer to a prod environment.
When doing unit test, it is important to know what are you going to test and write the tests based on your requirements. However, the second test, might looks like an integration test instead of an unit test, but I do not care to this point now!
Between your tests, I would recommend you to use the second option, because in the second unit test, you are testing your WebApi, as an WebApi, not as a class. For example suppose that you have a class with a method named X(). So how likely is it to write an unit test for it using Reflection? If it is completely unlikely, then writing an unit test based on Reflection is a waste of time. If it is likely, so you should write your test using Reflection too.
Moreover, using the second approach you are able to change the tech stack(For replace .Net with php) used for producing the WebApi, without changing you tests(This is what we expect from a WebApi too).
Finally, you should make a decision! how are you going to use this WebApi? How likely is it to call your WebApi using direct class instantiating?
Note:
It might be irrelevant to your question, but you should concentrate on your Asserts, too. For example asserting ResponseStatusCode and ResponseStatusMsg might not be needed and you can assert only one.
Or what will happen if obj is null? or obj has more than one member?
I'd say that they are not mutually exclusive. The first option is a classical unit test while the second is an integration test as involves more than a single unit of code.
If I had time to write either unit tests or integration tests, I'd pick unit tests as provides a more focused approach and gives, at least in my opinion, the best result from cost benefit.
In some particular projects where I had enough resources to write different suites of tests, I wrote both tests covering approaches. Where the second one would run without mocking anything (or maybe just the persistent storage) so I could test how all the components integrate together.
In relation to good practices, if you want to do real unit test, then you have no option but picking option one as no external dependencies are allowed (HttpClient is an external dependency).
Then, if the time and resources allow it, you could do integration testing for the most critical and/or complex paths.
If you are looking for some non programming You can use Postman, and can create collection of requests and can test multiple requests one by one.
You can use Swagger (aka OpenAPI).
Install Swashbuckle.AspNetCore from nuget.
using Microsoft.OpenApi.Models;
//in Startup.ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
});
}
//in Startup.Configure
public void Configure(IApplicationBuilder app)
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
}
Finally, add "launchUrl": "swagger", in launchSettings.json
I have domain classes that reference System.Diagnostics.Activity.Current.Id in my web api. It uses this value as a correlation Id once the object is serialized and stored in Cosmos. I have no issue getting this value when making calls into the API. However if I try to reference this value in my domain unit tests, the System.Diagnostics.Activity.Current is null. I have tried setting it in my unit test functions but it is still always null in my domain.
System.Diagnostics.Activity.Current = new System.Diagnostics.Activity("MyUnitTests");
I know System.Diagnostics.Activity.Current is driven from the current HttpContext but there also is no current http context in my domain unit tests. How can I make this work with my unit tests?
Example:
Domain Class
public class Company
{
public int CompanyId { get; private set; }
public string Name { get; private set; }
public string CorrelationId { get { return System.Diagnostics.Activity.Current.Id; } }
}
Unit Test
[TestMethod]
public void MyTest()
{
Company c = new Company();
Assert.AreNotEqual(string.Empty, c.CorrelationId);
}
This unit test throws a null reference exception because System.Diagnostics.Activity.Current is null.
I agree that using System.Diagnostics.Activity in a domain class is a code smell.
But I had a similar issue with my unit test. The tested business-logic code uses Activity.Current.AddBaggage:
Activity.Current.AddBaggage("CustomerRef","SomeCustomerReference");
Because only the logic is tested, no HttpRequest is involved and Activity.Current is NULL.
So I extended my unit test with the following base class (example is for XUnit):
public class BaseTest : IDisposable
{
private Activity unitTestActivity;
protected BaseTest()
{
unitTestActivity = new Activity("UnitTest").Start();
}
public void Dispose()
{
unitTestActivity.Stop();
}
}
This is a design issue around mixing concerns and is a possible code smell.
Current code is tightly coupled to run time concerns that are not present when unit testing in isolation.
You'll most likely need to run an integration test where actual requests are made with a valid context.
Assigning the id via a service abstraction would help loosen the coupling to run time concerns.
And remove the need to unit unit test a simple domain POCO used to store run time data.
I've got a controller with a lot of constructor injection:
public MoviesController(ISession session, IClientContext clientContext, PManager pManager, ISegmentationService segmentationService, IGeoLocator geoLocator, IBus bus)
{
_session = session;
_clientContext = clientContext;
_pManager = pManager;
_segmentationService = segmentationService;
_geoLocator = geoLocator;
_bus = bus;
}
From my understanding (just read about Mocking), I've got a lot of Mock object properties to manually set if I wish to make a comprehensive test suite based on this controller.
For one method I'm only using one service (I'd even like to automate that with little effort if possible):
public object Show(Guid id)
{
var movie = _session.Get<movie>(id);
return movie;
}
But in another there are many services being used - is there any way to set those Moq objects up quickly? I could really use some examples as I'm new to testing. It's an asp.net mvc project with webapi 1 bundled in (testing the webapi controller here)
As has been said in the comments, if you have common setup code, you can put it in a Setup method that is called automatically from your testing framework before each test. It's decorated with a Setup attribute if you're using Nunit TestInitialize if you're using MStest. If you're using XUnit then it's a bit different.
So, your class might look like this:
public class SomeTests {
Mock<ISession> _sessionMock;
Mock<IClientContext> _clientContextMock;
[Setup]
public void Setup() {
_sessionMock = new Mock<ISession>();
_clientContextMock = new Mock <IClientContext();
}
MovieController CreateSut() {
return new MovieController(_sessionMock.Object, _clientContextMock.Object, ...);
}
[Test]
public void TestSomething() {
_sessionMock.Setup(x=>...);
//...
var sut = CreateSut();
//...
}
}
If you're trying to get away from completely creating the mocks manually, then you might want to look at something like AutoFixture with AutoMoq. Which will automatically supply mock instances when creating objects that accept interfaces. AutoFixture can be quite useful, but there is a learning curve to using it effectively so you might want to look at a tutorial / quickstart.
You could also configure an IOC container to supply mock instances for your test project, although I've never gone down that route myself.
For your example, you only need to mock the session, and can leave all the other dependencies null, since their behaviour should be irrelevant to the behaviour you are testing:
Mock<ISession> mockSession = new Mock<ISesssion>();
MoviesController controller = new MoviesController(mockSession.Object, null,null,null,null,null);
There is no need for you to set up any mocks other than the ones you need for this particular test
I can't test any logoff, login, register action from AccountController with the new Microsoft Fake Framework without having this error message: System.Security.VerificationException: Operation could destabilize the runtime.
The unit test is real simple:
[TestMethod]
public void LogOff()
{
var AccountController = new AccountController();
RedirectToRouteResult RedirectToRouteResult;
//Scope the detours we're creating
using (ShimsContext.Create())
{
ShimWebSecurity.Logout = () => { };
var test = AccountController.LogOff();
RedirectToRouteResult = AccountController.LogOff() as RedirectToRouteResult;
}
Assert.IsNotNull(RedirectToRouteResult);
Assert.AreEqual("Index", RedirectToRouteResult.RouteValues["Action"]);
Assert.AreEqual("Home", RedirectToRouteResult.RouteValues["controller"]);
}
Also find this: http://social.msdn.microsoft.com/Forums/en-US/vsunittest/thread/f84962ea-a9b2-4e0d-873b-e3cf8cfb37e2 that talk about the same bug but no answer.
Thanks!
I asked the same question before VS2012 Update 1 was released (VerificationException when instantiating MVC controller during unit testing) and I got the response from a guy from Microsoft who said that they are working on it and it should be available in the next update. Well, nothing since then.
However, in order to get the result and to continue testing using Microsoft Fakes Framework, I wrapped the calls to MVC methods like those in the UrlHelper class with my own private methods that return primitive types like string and then Shim the unit test to give me a desired result. That way I never made a call to the underlying MVC infrastructure and I got the desired result. Also, you will need to remove System.Web.Mvc.Fakes reference otherwise VerificationException will keep popping up.
If you find this tedious then you should switch to a more mature unit testing framework like Moq or Rhino.
Our team is pretty new with testing. We are using scrum for the first time.
In the first sprint we are having 1 button on the gui. Pressing the button will cause to translate the content of a text file to a database (in a new thread/task). The gui will show the task is started and will poll for the state of the task.
We know how to unit test everything by using the TDD method. But when done we have to make integration tests.
What kind of tests do wee need execute in an integration test?
Do we need to check everything in the database is filled correctly by testing different files?
(IE: relationships, specific format which is stored as varchar(xxx), etc?)
If so: This could be pretty complex right? Because you dont have 1 or 2 input parameters, but you have a whole file (the content of the file i mean!!!) which is variable to test. You could make hundreds, or maybe thousands of tests for this one button press then. Worst thing: Most of those tests (i.e. formatting) is already tested on unit tests.
Most examples on internet show more gui like tests as integration test (or are the examples below acceptance tests?):
Login with correct username and password
- Input: name + password
- Expected output: A redirect to homepage
Login with incorrect username and/or password
- Input: name and password (incorrect)
- Expected output: A warning (login failed, incorrect username or password)
Your question covers MANY areas of trying to move to a better procedure for testing and environmental building. So to start with lets just touch on integration tests.
For integration tests you want an empty database for the most part, as you are testing that given a specific set of data existing you can query that data and get the desired result.
Lets use a simple example like a UserFinder class, you want to be able to find a user within the system with a given name and get a usable model back.
public class UserFinder
{
private SomeDbContext _dbContext;
public UserFinder(SomeDbContext dbContext)
{ _dbContext = dbContext }
public User FindUser(string name)
{ _dbContext.Find<User>(new FindUserByNameQuery(_dbContext, name)); }
}
Now in the above although its a pretty rubbish class we could test that given a user in the database with the name "Tester" that when you call FindUser("Tester") you should get back that user model.
So as part of this test you would want to start by setting up the expected user in the database, then you would create your user finder giving it a real database connection, then you would query the database with the name "Tester" and prove that you get that model back.
This way you can run that test in any context, be it the IDE a build script or command line, and you will get consistant results because your test is self contained, it sets up the scenario, runs the test and validates the acceptance criteria. Then once that test is done all data should be removed, the whole point being that you set up ONLY what you need for a given test, and generally set it up ONLY within that test.
Now ideally as integration tests and acceptance tests are alot slower to run than unit tests you only really want to test things that are not covered in a unit test, however at the same time you want to make sure you only test YOUR logic, like you could easily create the test scenario above using NHibernate and all you would prove is that NHibernate works, which is not the goal for you. It is impossible for US to tell YOU what you need to test as we dont know your project or what you are trying to achieve, but you should have some idea of your applications flow of logic, and if you see any points in that flow where it connects to a database or file system or passes over some other external boundry to the application where you have business logic, you probably want to put a test scenario in.
I would say you need to limit what exactly you are testing. In my spare time I'm working on a MVC4 project with EF5 as a backend, with repositories and data services. I have tried mocking EntityFramework behavior, but it just too complex. And generally, here people agreed that mocking DB context is pretty pointless. And Microsoft does testing on that anyway, so don't bother testing yourself what is already tested by MS.
In my project I've set up test project and a testing database for that. But I don't test repositories cause they are very transparent and close to EF. I'm testing my data services by giving them real context and connection string to a testing DB.
For every test class I'm dropping the database and re-create the structure. I'm re-populating the data for every test, so tests run against same data all the time.
This is considered to be an integration test, as here I'm going through a few layers, before I hit database. And in those tests I'm picking up all the possible problems I can have with the relationships in DB and other issues with DbContext.
But that as far as I go for integration testing - my controllers (and other objects) are tested with mocked-up dependencies (I'm using Moq).
And Once I'm a bit more further with UI, i'll probably write up some tests through Selenium for web-pages.
Here is some examples from my testing project:
[TestFixture]
class ProjectDataServiceTest
{
private ProjectDataService _projectDataService;
private DatabaseSeeder _seeder;
private SiteContext _context;
[TestFixtureSetUp]
public void FixtureSetUp()
{
_context = new SiteContext(); // connection string is taken from app.config file
_seeder = new DatabaseSeeder(_context);
_seeder.InitialiseDb(); // create database structure
ProjectRepository projectRepository = new ProjectRepository(_context);
_projectDataService = new ProjectDataService(projectRepository);
}
[SetUp]
public void TestSetUp()
{
_seeder.SeedDatabase(); // put some test data from a script
}
[TearDown]
public void TestTearDown()
{
_seeder.RemoveData(); // delete everything from all the tables
}
/**************** Tests are here! ********************/
[Test]
public void CheckDatabaseConnectivity()
{
Assert.Pass();
}
[Test]
public void GetNoProjectsForUser()
{ // should return no project for this user, as nothing is assigned
var user = _seeder.Users[0];
var projects = _projectDataService.GetUserProjects(user);
Assert.IsEmpty(user.UserProjectRoles);
Assert.IsEmpty(projects);
}
[Test]
public void GetAllProjetsForUser()
{
var user = _seeder.Users[2];
var projects = (List<Project>)_projectDataService.GetUserProjects(user);
int count = user.UserProjectRoles.Count;
Assert.AreEqual(count, projects.Count);
Assert.False(projects.Contains(_seeder.Projects[0]));
}
}
Controllers are tested in a plain-vanilla unit-testing fashion:
[TestFixture]
class ProjectsControllerTest
{
private ProjectsController _projectController;
private Mock<IProjectDataService> _projectDataService;
private Mock<ICurrentUserService> _currentUserService;
[SetUp]
public void SetUp()
{
MapperConfig.SetMappings();
_projectDataService = new Mock<IProjectDataService>();
_currentUserService = new Mock<ICurrentUserService>();
_currentUserService.Setup(s => s.GetCurrentAppUser()).Returns(new AppUser());
_projectController = new ProjectsController(_projectDataService.Object, _currentUserService.Object);
}
[Test]
public void InstanceOfProjectController()
{
Assert.IsInstanceOf<ProjectsController>(_projectController);
}
[Test]
public void Index()
{
var projects = new List<Project>() { new Project() { Name = "one" }, new Project() { Name = "two" } };
_projectDataService.Setup(s => s.GetUserProjects(It.IsAny<AppUser>())).Returns(projects);
var view = _projectController.Index();
Assert.IsInstanceOf<AutoMapViewResult<List<ProjectViewModel>>>(view);
}
}
And don't forget that no automated testing can replace real-person going through the system and clicking random buttons, trying to break things. Many times unit tests lack random input from a user. And that is very hard to imitate in a machine.