Arkadi
Arkadi

Reputation: 1357

Best practice in Unit Testing

I want to know is there any best practice in Unit Testing (C#/NUnit) to test solution like this: Imagine I have method GetOrderById(long orderId) in BLL (Business Logic Layer), which gives me an Order by it's Id :). So I need to write test that will call this method with hardcoded Id parameter, but for example var Id = 1; If there is no record with such id, test will fail

[Test]
public void GetOrderById() 
{
    //var id = ?

    Assert.DoesNotThrow(() => {
        _orderService.GetOrderById(id);
    });
}

I need some solution to make work any time, create temporary record maybe? or smth better solution..

any ideas?

Upvotes: 2

Views: 2619

Answers (3)

Hersker
Hersker

Reputation: 547

You are not providing much info on what you want to achieve with this test. I guess you want to test if the method is able to fetch an order from the database. This is, as David Arno mention, integration testing. In general, testing against the database or filesystem is bad and makes for slow and brittle tests. If you have decoupled your data access layer from the business layer with an interface, you can use Subsititue (in nUnit lib) and provide that to your buisness layer.

Fx:

IDataAccess interfaceStub = Substitute.For<IDataAccess>();

If that is not enough, like i think in this case, you want your data access layer to return something useful for your order service method GetOrderById. You could make a "testable version" of the data access layer.

Could be something like this:

    //A few simple tests
    [TestMethod]
    public void CheckThatOrderExist()
    {
        var service = new OrderServiceTestable();
        var order = service.GetOrderById(1);//This will be found in the list
        Assert.IsNotNull(order);
    }

    [TestMethod]
    public void CheckThatOrderDoesNotExist()
    {
        var service = new OrderServiceTestable();
        var order = service.GetOrderById(2);//This will not be found
        Assert.IsNull(order);
    }

    //Your data access layer
    public class OrderService
    {
        protected virtual IList<Order> OrderList { get; set; }

        public Order GetOrderById(int id)
        {
            return OrderList.SingleOrDefault(x => x.Id == id);
        }
    }

    //Order object
    public class Order
    {
        public int Id { get; set; }
    }

    //This class inherits the order service and over write the list of orders. An instance of this class is used in the tests.
    public class OrderServiceTestable : OrderService
    {
        protected new List<Order> OrderList;

        public OrderServiceTestable()
        {
            OrderList = new List<Order> {new Order {Id = 1}}; //This will overwrite the list of orders because its virtual in the order service class
        }
    }

See what i did? By inheriting the real class and overwriting properties or methods that are virtual. You can make your testable overwrite any data, at the same time you are able to test the actual method in the OrderService class. This will make for robust and blindingly fast tests. You dont test your data access layer, but only the method in your business layer. It may, however require some work on your part.

With this say, i still recommend decoupling with interfaces.

Upvotes: 2

Mitklantekutli
Mitklantekutli

Reputation: 410

For the first you have to implement Dependency Inversion principle and after that you will be able to use MockObjects via Dependency Injection.

Upvotes: 0

David Arno
David Arno

Reputation: 43254

So I need to write test that will call this method with hardcoded Id parameter, but for example var Id = 1; If there is no record with such id, test will fail

This indicates that you either aren't programming to interfaces, or that you are creating an integration test, rather than a unit test. Quite likely, it's a combination of both.

You BLL should only have a reference to the Data Access Layer (DAL), via interfaces. It should not have a hard-coded references to actually classes. Those concrete classes should only be supplied at run-time, via injection. Thus, when testing, you supply a mocked DAL, which will have a record with a particular Id when testing supplying a correct Id works. Likewise, it will not have a record with that Id when testing the BLL correctly handles a missing record.

Because the DAL is mocked, it's completely under the control of the tests and thus it's easy to set up these success/failure conditions.

Upvotes: 2

Related Questions