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
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?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 withNInject
?) 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.
1 Answer 1
- 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,...)
-
\$\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 afactory
, maybeProductServiceTestAssertResultsFactory
? (but this feels horrid) \$\endgroup\$askrich– askrich2015年07月28日 14:10:08 +00:00Commented Jul 28, 2015 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\$askrich– askrich2015年07月28日 14:20:11 +00:00Commented Jul 28, 2015 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– BatteryBackupUnit2015年07月28日 15:02:38 +00:00Commented Jul 28, 2015 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– BatteryBackupUnit2015年07月28日 15:04:28 +00:00Commented Jul 28, 2015 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– BatteryBackupUnit2015年07月28日 15:30:37 +00:00Commented Jul 28, 2015 at 15:30
Explore related questions
See similar questions with these tags.