Reputation:
I was under the impression that mocking is faking data calls thus nothing is real. So when I trying to create my own Unit Test that are very similar to what other developers on my team are doing, I am thinking that this is not correct to be newing up the service.
[Test]
public void TemplateService_UpdateTemplate_ContentNameNotUnique_ServiceReturnsError()
{
Template Template = new Template()
{
TemplateId = 123,
};
// Arrange.
TemplateUpdateRequest request = new TemplateUpdateRequest()
{
ExistingTemplate = Template
};
var templateRepo = new Mock<ITemplateRepository>();
var uproduceRepo = new Mock<IUProduceRepository>();
templateRepo.Setup(p => p.IsUniqueTemplateItemName(215, 456, "Content", It.IsAny<string>())).Returns(false);
templateRepo.Setup(p => p.UpdateTemplate(request)).Returns(1);
// Act.
TemplateService svc = new TemplateService(templateRepo.Object, uproduceRepo.Object);
TemplateResponse response = svc.UpdateTemplate(request);
// Assert.
Assert.IsNotNull(response);
Assert.IsNotNull(response.data);
Assert.IsNull(response.Error);
}
So my issue is with this code:
TemplateService svc = new TemplateService(templateRepo.Object, uproduceRepo.Object);
Should the TemplateService really be newed up? What "if" the Service ended up hitting a database and/or file system? Then it becomes an integration test, and no longer a unit test, right?
TemplateResponse response = svc.UpdateTemplate(request);
Also, how do I really control whether this is truly going to pass or not? It is relying on a service call that I didn't write, so what if there is a bug, or encounters a problem, or return NULL, which is exactly what I do not want! ?
Upvotes: 0
Views: 4696
Reputation: 10851
If you're testing TemplateService
, then YES it should be newed up. How else can you test your actual implementation? The point here is to only test TemplateService
and not it's dependencies (unless it's an integrations test).
So if you got a repo like IUProduceRepository
it should be mocked, simply to ensure that you're not writing to some database, and to make sure that you can create a specific scenario.
Your goal is to test that TemplateService
is doing the right thing, based on a specific scenario. So let's say that your IUProduceRepository
is throwing an error that your TemplateService
should handle. Then you should mock that error in your IUProduceRepository
and test your implementation of TemplateService
and make sure that it handles it as expected.
So to answer your questions...
Should the TemplateService really be newed up? What "if" the Service ended up hitting a database and/or file system? Then it becomes an integration test, and no longer a unit test, right?
Yes, I would say that it would be an integrations test. You mock things to make sure that the service isn't writing to DB.
Also, how do I really control whether this is truly going to pass or not? It is relying on a service call that I didn't write, so what if there is a bug, or encounters a problem, or return NULL, which is exactly what I do not want! ?
Your goal is to make sure that the class you test is doing what you expect in a specific scenario. Let's say that your repo is throwing an exception for some reason, then you might want to test that your service can handle that (or at least test that it's doing what's expected).
public class TemplateService : ITemplateService
{
ITemplateRepository _templateRepo;
IUProduceRepository _produceRepo
public TemplateService(ITemplateRepository templateRepo, IUProduceRepository produceRepo)
{
_templateRepo = templateRepo;
_produceRepo = produceRepo;
}
public TemplateResponse UpdateTemplate(Template template)
{
try
{
var result = _templateRepo.Update(template);
return result;
}
catch(Exception ex)
{
// If something went wrong. Always return null. (or whatever)
return null;
}
}
}
[Test]
public void TemplateService_UpdateTemplate_ShouldReturnNullOnError()
{
Template template = new Template() // note that I changed the variable name.
{
TemplateId = 123,
};
// Arrange.
TemplateUpdateRequest request = new TemplateUpdateRequest()
{
ExistingTemplate = template
};
var templateRepo = new Mock<ITemplateRepository>();
var uproduceRepo = new Mock<IUProduceRepository>();
// Mock exception
templateRepo.Setup(p => p.UpdateTemplate(request)).Throw(new ArgumentException("Something went wrong");
// Act.
TemplateService svc = new TemplateService(templateRepo.Object, uproduceRepo.Object);
TemplateResponse response = svc.UpdateTemplate(request);
// Assert.
// Make sure that the exception is handled and null is returned instead.
Assert.IsNull(response);
}
The ONLY thing you actually tested in the case above is that your service will return null
if there's an error in your repo. That way, you have designed a test for a specific scenario, and made sure that your service is doing what's expected when that scenario occurs.
I know that you specifically mentioned that you didn't want it to return null
, but it's more of the concept I'm talking about. So let's say that the repo returns null
... Write a test for it. Mock the repo to return null
, and then test that your service is doing what it should (logging, etc).
so what if there is a bug, or encounters a problem, or return NULL, which is exactly what I do not want! ?
That's what unit testing is all about. Find a scenario, and then make sure that the class you're testing is doing it's part. Mock your dependencies to create that specific scenario.
When I write my tests I have a lot of tests that simply asserts that a method was called. Let's say that I have a service, which only responsibility is to call the repo. Then you might want to write three tests
Upvotes: 1
Reputation: 750
Interaction testing is a form of unit testing where you provide fake dependencies for everything (or some things, or only the really expensive things like databases or disks, there are a lot of different interpretations regarding this) except the thing you actually want to test.
In your example, you're testing whether the TemplateService code behaves correctly. The test provides fake collaborators (the repositories), which you can setup so they return different things in different tests. That way, you can verify whether the TemplateService behaves correctly under these circumstances.
If your concerns about new-ing up the TemplateService are that there might be some remaining calls to a real database in the TemplateService code itself, that's a design issue of the TemplateService code. Those calls should not happen in the TemplateService but should be implemented on a collaborating object (exactly so you can fake them in focused isolated tests for your service).
Writing a unit test without new-ing up the system-under-test provides zero value. If every object is a fake, how can you test your actual production code?
On your point about how those mocked services are actually implemented (Do they throw nulls? Any bugs? Do they return NULL?), that's a question you can solve by
a) Writing integration tests (warning: this does not scale well, see related reading on why integrated tests are a scam).
or b) using something J.B. Rainsberger calls "Contract tests" (see related reading below) to verify whether the actual collaborator objects actually behave in a way consumers expect them to do.
Related reading:
Upvotes: 0
Reputation: 171
I recommend you to read the book The Art of Unit Testing: with examples in C# to learn good practice.
In your example, you are testing TemplateService
class.
Your concern is what if TemplateService
calls database. It depends on how this class is implemented. From the example and mock setup, I can understand that the details of ITemplateRepository
is responsible for database calling and that is why UpdateTemplate
and IsUniqueTemplateItemName
are mocked.
If you want to avoid the Null check, then you can check whether the svc.UpdateTemplate(request)
calls the method UpdateTemplate
of ITemplateRepository
with its parameter.
It should be similar as follows
templateRepo.Verify(u => u.UpdateTemplate(It.Is<TemplateUpdateRequest>(r => r.ExistingTemplate.TemplateId == 123)),Times.Once);
You can verify other method calls that you have mocked.
Upvotes: 0
Reputation: 1256
A unit test is designed to test a unit of functionality, which often is (but definitely need not be) a single class (since we should follow the Single Responsibility Principle). Therefore, it's fine to new up the class(es) that you're trying to test - it's the dependencies that you need to mock/stub out, which in your case is what you've done with the two repositories.
Whether your call
TemplateResponse response = svc.UpdateTemplate(request);
ends up hitting the DB is a question of using Dependency Injection correctly - your team needs to be disciplined in using explicit dependencies (ie. the repositories which are constructor injected), and not newing up repositories within a class.
And regarding whether your test is going to pass or not - if the class only has explicit dependencies (which are mocked out in your test), then only the logic within the call will be tested, which is exactly what you want!
Upvotes: 0