22

I'm writing unit tests for one of our products and have been used Moq to successfully mock connections to Entity Framework. However, I've come across the following method:

public static productValue findValues(string productName, string dbConnectionString)
{
    try
    {
        SqlConnection conn = new SqlConnection(dbConnectionString);
        conn.Open();
        //Do stuff 
    }
}

Which accesses our database inside that method using a passed connection string. Is it possible to setup a mock DB using Moq and create a connection string which points to the mocked DB? I've trying doing something along the lines of

var mockSqlConnnection = new Mock<SqlConnection>();

Though I'm unsure if this is the correct approach, as this would mock the connection itself rather than the DB.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Novastorm
  • 1,439
  • 3
  • 26
  • 40
  • 1
    That's isn't possible I believe as SqlConnection is a sealed class and so can't be mocked. You could mock IDBConnection, but there would still be lots more to do: if you want to unit test this method, you may have to refactor it a bit. – stuartd Jan 22 '16 at 10:38
  • 1
    You're absolutely right, SqlConnection is a sealed class – Novastorm Jan 22 '16 at 12:01
  • 1
    [One of the best ways to get the database out of your way is to hide data access behind abstracted interfaces that can be mocked in business logic testing](http://codebetter.com/jeremymiller/2005/10/12/unit-testing-business-logic-without-tripping-over-the-database/). – Daniel Dušek Jan 22 '16 at 13:33
  • 1
    Highly recommeded to use IDbConnection instead of SqlConnection – maxspan Apr 15 '20 at 13:23

5 Answers5

18

I had a similar problem.

I introduced an SqlDataContext wrapper around the SqlConnection which inherited from and ISqlDataContext interface:

class SqlDataContext : ISqlDataContext {

    private readonly SqlConnection _connection;

    public SqlDataContext(string connectionString)
    {
        _connection = CreateConnection(connectionString);
    }

    public IDataReader ExecuteReader(string storedProcedureName, ICollection<SqlParameter> parameters)
    {
       // execute the command here using the _connection private field.
       // This is where your conn.Open() and "do stuff" happens.
    }

    private SqlConnection CreateConnection(string connectionString)
    {
        if (string.IsNullOrEmpty(connectionString))
        {
            throw new ArgumentNullException("connectionString");
        }

        return new SqlConnection(connectionString);
    }
}

interface ISqlDataContext
{
    IDataReader ExecuteReader(string storedProcedureName, ICollection<SqlParameter> parameters);
}

You can add overloads to the ISqlDataContext as you need.

What this means is that you can then mock the ISqlDataContext as requires using Moq or similar and return mock values.

Means you can then test your repository or anything else that hits the database through the SqlConnection without actually having to hit the database.

The other advantage is that you can inject ISqlContext with DI / IoC as needed.

Graham
  • 1,497
  • 2
  • 16
  • 36
  • Fantastic! I'll give this a go – Novastorm Jan 22 '16 at 11:42
  • 13
    While this implementation will let you mock the creation of the IDataReader, you now have a leaky implementation that will result in error messages along the lines of "unable to get a connection from the pool" (http://stackoverflow.com/questions/15848239/how-to-solve-max-connection-pool-error). This is because you aren't "disposing" your SqlConnection (or SqlCommand). I'd recommend also implementing the IDisposable interface (https://msdn.microsoft.com/en-us/library/system.idisposable(v=vs.110).aspx) and putting a "using" statement around your use of this SqlDataContext object. – Sean Jul 25 '16 at 09:59
  • I appreciate the Entity Framework pattern of calling your wrapper (to your external resource handler) Context. If you inherit ISqlDataContext with IDbConnection, you will get IDisposable and broader compatibility. – TamusJRoyce Mar 23 '18 at 15:03
7

late but why not with mstest:

[TestMethod]
MyTestWithInternSqlConnection()
{
   using (ShimsContext.Create())
   {
      // simulate a connection
      ShimSqlConnection.AllInstances.Open = connection => { };
      string commandText;

      // shim-Mock all called methods
      ShimSqlCommand.AllInstances.ExecuteReader = command =>
      {
         commandText = command.CommandText;
         return new ShimSqlDataReader();
      };

      int readCount = 0;
      ShimSqlDataReader.AllInstances.Read = reader => readCount == 0;
      ShimSqlDataReader.AllInstances.GetSqlStringInt32 = (reader, i) =>
      {
         readCount++;
         return "testServer";
      };

      var theReadedString = AMethodUnderTestThatReadsFromDatabaseAString();
      Assert.IsTrue(theReadedString == "testServer");
   }
}

you need to add a reference to System.Data and then add a Fake for it.

https://msdn.microsoft.com/en-us/library/hh549175.aspx Better is it, if you change the implementation and you can change the used read layer but ...

Daniel Bernsons
  • 650
  • 7
  • 20
tire0011
  • 1,048
  • 1
  • 11
  • 22
2

Have a look at the Repository Pattern, essentially you would mock the data in your consuming classes, rather than worrying about the implementation of talking to the database.


Essentially, you would have a repository

namespace ContosoUniversity.DAL
{
    public class StudentRepository : IStudentRepository, IDisposable
    {
        private SchoolContext context;

        public StudentRepository(SchoolContext context)
        {
            this.context = context;
        }

        public IEnumerable<Student> GetStudents()
        {
            return context.Students.ToList();
        }

        // ... more

Which is then consumed in other classes:

   public class StudentController : Controller
   {
      private IStudentRepository studentRepository;

      public StudentController(IStudentRepository studentRepository)
      {
        this.studentRepository = studentRepository;
      }

And used as:

  public ViewResult Index(string sortOrder, string currentFilter, string searchString, int? page)
  {
     var students = from s in studentRepository.GetStudents()
                    select s;

The full example is in the link at the top.


So then you would pass a mocked repository into your class:

// arrange
var mockedRepo = new Mock<IStudentRepository>();
// configure

// act
var controller = new StudentController(mockedRepo.Object);
// do stuff

// assert
NikolaiDante
  • 18,469
  • 14
  • 77
  • 117
  • 1
    If he want to test a class or method that uses a connection directly as I believe he is then he'll run into difficulties. It looks as though the method he is testing is a repository method anyway. – Graham Jan 22 '16 at 10:51
  • 10
    If he wants to test the repository? – chris31389 Sep 19 '16 at 08:54
  • 1
    @chris31389 - I was thinking the same thing, but then realized the SqlDataContext essentially IS the Repository. We've just added one layer of abstraction to make the original function testable. Now, how do we go about testing 'ExecuteReader' in the SqlDataContext in Graham's solution? It looks like we're encountering the same problem. – th3morg Nov 13 '19 at 17:47
2

How to Moq a SqlConnection: wrap at SqlConnection in a protected virtual method that has a return type of IDbConnection, so that it can be setup from its mocked parent class.

We're doing a similar thing as the accepted answer, but exclusively with Moq (and Moq.Protected).

In your data repository class break-out your connection into it's own function. In this example, it's called GetConnection. Make this a protected virtual method so we can mock it later. And add a return type of IDbConnection. This is the key.

  public Stuff FindValueMethod(string product)
  {
    ...
    try
    {
      using (var connection = GetConnection())
      {
          var result = await FindValue(connection, params);
          //Do stuff
      }
      
      return stuff;
    }
    ...
    
    protected virtual IDbConnection GetConnection()
    {
        return new SqlConnection(injectedSettings.connectionString)
    }

Later, in the data repository unit tests, do the dependency injection as you normally would, but wrap your dependencies with "Mock<>". In order to mock protected virtual classes, we need them to be extensible from the main DataRepository class. So we also have to mock the data repo. Be sure to add a mock database connection, too.

using Moq;
using Moq.Protected;
...
    private readonly Mock<DbConnection> _connection;
    private readonly Mock<ILogger<DataRepository>> _logger;
    private readonly Mock<Param> _param;
    private readonly Mock<DataRepository> _dataRepository;

    ...

    DataRepositoryTestsConstructor()
    {
        _connection = new Mock<DbConnection>();
        _logger = new Mock<ILogger<DataRepository>>();
        _param = new Mock<Param>();

        //use the Mock.Object for dependency injections into the Mock repo.
        _dataRepository = new Mock<DataRepository>(_logger.Object, _param.Object);
    }

    ...

    [Fact]
    public async Task FindValueMethod_Returns_ProductPrice()
    {
       //Arrange
       _dataRepository.Protected().Setup<IDbConnection>("GetConnection").Returns(_connection.Object);

       //Act
       var result = await _dataRepository.Object.FindValueMethod("rhubarb");

       //Assert
       Assert.NotNull(result);
    }

In the unit test above, once we have a mock data repository, then we can set up a protected method with a return type of IDbConnection. And return the Mock connection object. Now, the debugger will slide over the using (var connection = GetConnection()) line like butter.

It's a bit of work to mock the repo and the repo dependencies. And refactor your code to have a protected virtual database connection method. But that one-liner in the Arrange section is worth it.

Benxamin
  • 4,774
  • 3
  • 31
  • 30
0

If your connection string variable on your main project is calling the configuration manager you would just set the configuration manager setting on the unit test with what you have on the main project. Afterwards, reference System.Configuration and no need to create any config files.

using System.Configuration;
using Xunit;

namespace xUnitExample
{
    public class ExampleTests
    {
        [Fact]
        public void TesReturnSomething()
        {
            string value = "connection";
            ConfigurationManager.AppSettings["key"] = value;
            //Your Test
        }
    }
}
Wes
  • 1,847
  • 1
  • 16
  • 30