public static void Main() { // This code shows that both service implementations have access to the database and // that they still fulfill the same interface contract IReportingService untestableReportingService = new Problem.ReportingService(); Console.WriteLine( "Students enrolled, queried via the untestable service implementation: {0}", untestableReportingService.GetNumberOfEnrolledStudents()); IReportingService testableReportingService = new Solution.ReportingService(); Console.WriteLine( "Students enrolled, queried via the testable service implementation: {0}", testableReportingService.GetNumberOfEnrolledStudents()); Console.ReadKey(); }
public void AlasHowDoYouTestThisWithoutAWorkingDatabaseConnection() { // Arrange // Step one: // Arrange the test data the linq statements within the ReportingService instance should run against. var studentWithEnrollment = new Student { Enrollments = new List<Enrollment> { new Enrollment() } }; var studentWithoutEnrollment = new Student { Enrollments = new List<Enrollment>() }; var testStudents = new List<Student> { studentWithEnrollment, studentWithoutEnrollment }.AsQueryable(); // Step two: Make the test data available as an IQueryable // or in other words: As the type that linq runs its queries against. // At this point we can already predict how many students should be counted as enrolled // -> We put one student with enrollments and one student without enrollments into our set of test data, // so once we hook the ReportingService up with the test data, the answer to GetNumberOfEnrolledStudents() // should be 1. var expectedNumberOfEnrolledStudents = 1; // Step three: // Create a mock DbSet var mockStudentsDbSet = new Mock<DbSet<Student>>(); // A DbSet implements IQueryable to allow the execution of linq queries against the DbSet itself. // That's good news, because that means that we can just hook up our selfmade IQueryable 'students' // to our mocked DbSet, which means that any time a linq query is executed against our mocked DbSet it really // gets executed against our selfmade IQueryable 'students' which - as you might remember - contains exactly // the test data we want it to contain and does certainly not require any database connection whatsoever. // // The code to hook up our IQueryable to the mocked DbSet is kinda ugly though... mockStudentsDbSet.As<IQueryable<Student>>().Setup(queryable => queryable.Provider).Returns(testStudents.Provider); mockStudentsDbSet.As<IQueryable<Student>>().Setup(queryable => queryable.Expression).Returns(testStudents.Expression); mockStudentsDbSet.As<IQueryable<Student>>().Setup(queryable => queryable.ElementType).Returns(testStudents.ElementType); mockStudentsDbSet.As<IQueryable<Student>>().Setup(queryable => queryable.GetEnumerator()).Returns(testStudents.GetEnumerator); // Step four: // Next we need to mock the DbContext or in our case the SchoolContext. We can't simply replace the Students // DbSet in an original SchoolContext because the DbContext part would still try to open a connection to the // database - which our unit test does not have access to. var mockSchoolContext = new Mock<SchoolContext>(); // Step five: // Hook out mocked students DbSet into out mocked SchoolContext so any call against mockSchoolContext.Students // gets redirected to our mocked students DbSet. mockSchoolContext.Setup(db => db.Students).Returns(mockStudentsDbSet.Object); // Important: The DbSet-Properties within the DbContext / SchoolContext need to be declared as // virtual or Moq will be unable to mock the properties. // i.e. // public virtual DbSet<Student> { get; set; } // instead of // public DbSet<Student> { get; set; } // Database first entity framework automatically marks the properties as virtual. // If you use the entity framework code first approach you have to ensure this yourself. // Step six: // Now we need to hook up our ReportingService with our mocked SchoolContext. This can be achieved via // dependency injection. This example uses a simple form of constructor injection. Depending on your // situation a more complex solution might be required. IReportingService reportingService = new ReportingService(mockSchoolContext.Object); // And that's it! We're done! // The following code now executes against our test IQueryable and returns the result predicted // above. Through mocking and decoupling we are now able to test any linq statements written against // entity framework while completely ignoring the underlying database. // Act var actualNumberOfEnrolledStudents = reportingService.GetNumberOfEnrolledStudents(); // Assert Assert.AreEqual(expectedNumberOfEnrolledStudents, actualNumberOfEnrolledStudents); }