3
\$\begingroup\$

I have been a developer for many years and get core development concepts, as well as unit testing ones such as DI/Ioc, Mocking etc. I also understand the values, importance of unit testing and writing good tests, as well as asking myself often: "What Would Roy Do?".

I am however slightly unsure on some approaches and principles to follow, and am not 100% sure of what I should be looking for; I am using NSubstitute and NUnit.

I am also using the EntityFramework with a class mapping over the entities to provide extra functionality and strongly-type it to other domain models; these properties have been excluded for brevity.

Is it considered correct to create an interface for my domain object? If I want to write unit tests against services which return a domain object, or check the values of these objects (when they return a list of domain objects) I think it seems right, but this article seems to suggest I shouldn't.

Questions

  1. How should the value generation be done? I'm not sure how we should make it react to different input parameters. I've implemented a way it could be done, but part of me feels this isn't right. What happens when I have more people added to it as I went to test, for example? Should I expand this section?

  2. Where should the value generation be done? Leaving it in the function feels wrong, but is clear as to how the values are generated/returned. Moving it to a function would make the test cleaner, and therefore easier to read (?), and usable in other tests? Or should I inject it (maybe with NInject?) but this would require the creation of another service?

Interface

    Public Interface IProductService
        Function GetProductItems(userAs String) As List(Of IProductItem)
    End Interface

Service Code

Namespace Services
    Public Class ProductService
        Implements IProductService

        Public Function GetProductItems(userAs String) As List(Of IProductItem) Implements IProductService.GetProductItems
            Dim result = New List(Of IProductItem)

            Using con As New ProductEntities(Settings.Settings.ConnectionString)
                For Each item In con.Product_Item.Where(Function(a) a.User.Equals(domainAndRacf, StringComparison.OrdinalIgnoreCase))
                    result.Add(New ProductItem(item))
                Next
            End Using

            Return result
        End Function
    End Class

Domain Model Interface

Public Interface IProductItem
    ReadOnly Property Id As Integer
End Interface

Domain Model

Namespace Classes
    Public Class ProductItem
        Implements IProductItem
        Private _entity As Product_Item

        Public ReadOnly Property Id As Integer Implements IProductItem.Id
            Get
                Return _entity.Id
            End Get
        End Property

        ## Constructors and other properties ommitted ##
   End Class
End Namespace

Test Sample

    Imports NSubstitute
    Imports NUnit.Framework

    Namespace Services

        <TestFixture()>
        Public Class ProductServiceTests

            <TestCase("userA")>
            <TestCase("userB")>
            Public Sub GetProductItems_WithValidUser_ItemsForThatPerson(user As String)
                ' Arrange
                Dim ProductService = New ProductService()

                ''''''''''''''''''''''''''''''''''
                ''''''vvv Value Generation vvv''''
                Dim prod = Substitute.For(Of IProductItem)

                Dim subProductService = Substitute.For(Of IProductService)()

                subProductService.GetProductItems("userA").Returns(
                        New List(Of IProductItem) From {prod}
                    )

                subProductService.GetProductItems("userB").Returns(
                        New List(Of IProductItem)
                    )

                ''''''^^^ Value Generation ^^^''''
                ''''''''''''''''''''''''''''''''''

                ' Act
                Dim prodItems = ProductService.GetProductItems(user)

                ' Assert
                CollectionAssert.AreEqual(prodItems, subProductService.GetProductItems(user))
            End Sub

        End Class
    End Namespace

Extension

An extension to the above (which I hope is still classed as related, due to following a similar theme of principles and approaches), is about testing against a database accessed through the EntityFramework DatabaseFirst.

There are other posts talking about mocking the entire framework, but this to me feels overkill. Is there a better way than creating hand-written substitutions for every single table and value within there?

I'm wanting to make the tests as lean and independent as I can, so when new developers join the team it is easy and straightforward for them to pick up.

\$\endgroup\$
1
\$\begingroup\$
  • dont use/apply the dependency injection container in class-level unit tests. Instead, do so on component or system level tests. For these, there are other test tools which may be a better fit (xBehave.net, Specflow, Machine.Specifications...)
  • Improve the implementation regarding Separation of Concern / Single responsibility.
    • why does the service itself know how to open a connection?
    • if it's a web-service/wcf-service ("request response" style implementation) you should inject the db context with a per request scope (create all instances per request)
    • if it isn't a web app/wcf-service you might still consider a "request-response" pattern.
    • if it's not a good fit, you should at least extract creation of the connection to a factory.

Now if you do this, i'd suggest the following tests:

GetProductItems_MustOpenConnection()
GetProductItems_MustCloseConnection()

next, according to taste:

GetProductItems_WhenProductBelongsToUser_MustReturnProductItem()
---> setup a product which belongs to user. Test that it is returned as ProductItem

GetProductItems_WhenProductDoesNotBelongToUser_MustReturnProductItem()
--> setup a product to belong to different user. Test that it doesn't return any item.
    (verifies that we're not returning products not belonging to the user)

GetProductItems_WhenMultipleProductsBelongsToUser_MustReturnAsProductItems()
---> setup multiple product which belongs to user. Test that it is returned as ProductItem
     (verifies that actually multiple are returned and not just a one).

alternatively:

GetProductsItems_MustReturnItemsBelongingToUser()
---> setup products which belong to user and such which don't belong to user.
     Verify that all (and only) the ones which belong to the user are returned.

Now regarding mocking the database. What we've used to do is abstract all queries in their custom query class. For those, we created a clean in-memory database which we set up (filled with data) in the unit test (test data specific to a test). Then we'd test a query with this data. Mocking the interface of DbContext and the likes is usually very complicated and easy to get wrong. Some statements might throw a NotSupportedException during runtime -in this regard it's very hard to keep your fake/mock database in sync with the real thing. So that's why i prefer actual integration tests for this.

Note: an in-memory database often also behaves somewhat differently from the real thing, of course you're free to use a real database for an integration test (takes longer, more dependencies,...)

\$\endgroup\$
  • \$\begingroup\$ Thanks for the answer @BatteryBackupUnit, I'll have a good read through, in context, and see how it goes. One question I'm still not 100% sure of is whether or not I'm doing the value generation stage correct, in terms of the return values. Is this the correct way to do it, or should this be farmed off into a factory, maybe ProductServiceTestAssertResultsFactory? (but this feels horrid) \$\endgroup\$ – Richard Simpson Jul 28 '15 at 14:10
  • \$\begingroup\$ Also, to pick up on a point: dont use/apply the dependency injection container in class-level unit tests is there a reasoning behind this, or a reference as to why this might generally be considered a bad approach? \$\endgroup\$ – Richard Simpson Jul 28 '15 at 14:20
  • 1
    \$\begingroup\$ @RichardSimpson reason: it's not possible to re-use the container configuration in a class-level unit test. So there's no benefit at all in including it in the test. With Component / System wide tests this is different, there you can usually reuse almost all of the container configuration (some stuff might need to be Rebind-ed in order to replace it with a fake/mock). \$\endgroup\$ – BatteryBackupUnit Jul 28 '15 at 15:02
  • 1
    \$\begingroup\$ @RichardSimpson i don't really get what you mean by value generation. If you mean the setup of a dependency (for example: the products with their properties): this should be done specific to a test case. Setup should only contain what's necessary for this specific test case. So if there's a property which is normal filled with some data in this scenario, but it's not necessary to make the test pass: don't set it up! This is basically the first rule of Test-Driven-Development: You only code what it's absolutely needed. \$\endgroup\$ – BatteryBackupUnit Jul 28 '15 at 15:04
  • 1
    \$\begingroup\$ Tailor the value setup specific to one test method and include it in the test method. If you have to do a lot of setup then usually Separation of Concern is not good (means you should refactor). However, with database access this can sometimes get quite complicated (since you're doing kind of an object-navigation/traversal). So there may come a point where you choose to design these tests differently. \$\endgroup\$ – BatteryBackupUnit Jul 28 '15 at 15:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.