8

When writing unit test, Is there a simple way to ensure that nothing unexpected happened ?

Since the list of possible side effect is infinite, adding tons of Assert to ensure that nothing changed at every steps seems vain and it obfuscate the purpose of the test.

I might have missed some framework feature or good practice.
I'm using C#7, .net 4.6, MSTest V1.

edit: The simpler example would be to test the setter of a viewmodel, 2 things should happen: the value should change and PropertyChanged event should be raised. These 2 things are easy to check but now I need to make sure that other properties values didn't changed, no other event was raised, the system clipboard was not touched...

Titan
  • 181
  • 2
  • 9
  • What specifically do you mean by 'Nothing' happened? If you execute code 'something' is going to happen. What do you want to verify did not change? A variable or? – Josh Adams Feb 02 '18 at 17:53
  • As you said, the list of side-effcts is infinite. So you should check, that these do *not* happen. – MakePeaceGreatAgain Feb 02 '18 at 17:53
  • 2
    Each test should check only one thing, so you don't care about anything else other than this one thing. I don't care if `Add(1,5)` used integrals to figure it out, as long as I get `6`, it's fine... – FCin Feb 02 '18 at 17:53
  • If you find that you need something like this, my guess is that you don't have adequate test isolation. – stephen.vakil Feb 02 '18 at 17:59
  • A bit of a side comment, but that's one of the advantages of using an event-based model, like in CQRS+ES. Everything that happens on an entity happens in the form of an event, so it's very easy to know if nothing happened: just look if any event was generated! – devoured elysium Feb 02 '18 at 18:08

5 Answers5

4

You're missing the point of unit tests. They are "proofs". You cannot logically prove a negative assertion, so there's no point in even trying.

The assertions in each unit test should prove that the desired behavior was accomplished. That's all.

If we reduce the question to absurdity, every unit test would require that we assert that the function under test didn't start a thermonuclear war.

Unit tests are not the only kind of tests you'll need to perform. There are functional tests, integration tests, usability tests, etc. Each one has its own focus. For unit tests, the focus is proving the expected behavior of a single function. So if the function is supposed to accomplish 2 things, just assert that each of those 2 things happened, and move on.

Mark Benningfield
  • 2,800
  • 9
  • 31
  • 31
  • "You cannot logically prove a negative assertion" is itself a negative assertion. So, if it were true, it would itself be unprovable. I think what you meant to say is that you cannot prove that something doesn't exist (e.g. a side-effect in this case). – Wyck Feb 02 '18 at 18:59
  • In fact, using a referentially transparent programming language, proving exactly that is possible. This is possible in Safe Haskell. – Bjartur Thorlacius Oct 19 '19 at 21:34
2

Some addition to your edit:

In Test Driven Development you are writing only code, which will pass the test and nothing more. Furthermore you want to choose the simplest possible solutoin to accomplish this goal.

That said you will start most likely with a failing unit-test. In your situation you will not get a failing unit test at the beginning.

If you push it to the limits, you will have to check that format C:\ is not called in your application when you want to check every outcome. You might want to have a look at design principles like the KISS-principle (Keep it simple, stupid).

Link
  • 1,307
  • 1
  • 11
  • 23
  • 1
    This was perfect for me. I am using a factory pattern that returns a service class for given situations. One situation is a NoOp. I got stuck on "testing" that it was a no-op and your "back to basics" approach got me back on track. My failing unit test was calling the method (method didn't exist). I fixed that by creating the method and nothing else. Test done :) Sometimes it is easy to get tunnel vision and miss the forest for the trees. – DanCaveman Dec 12 '20 at 15:59
2

One of the options to ensure that nothing 'bad' or unexpected happens is to ensure good practices of using dependency injection and mocking:

[Test]
public void TestSomething()
{
    // Arrange
    var barMock = RhinoMocks.MockRepository.GenerateStrictMock<IBar>();
    var foo = new Foo(barMock);
    // Act
    foo.DoSomething();
    // Assert
    ...
}

In the example above if Foo accidentally touches Bar, that will result in an exception (the strict mock) and the test fails. Such approach might not be applicable in all test cases, but serves as a good addition to other potential practices.

ethane
  • 2,329
  • 3
  • 22
  • 33
Serge Semenov
  • 9,232
  • 3
  • 23
  • 24
  • I can't use rhinomock now, but their StrictMock is a part of what I was looking for. Thanks – Titan Feb 05 '18 at 09:59
1

If the scope of "check that nothing else happened" is to ensure the state of the model didn't change, which it appears is the case from the question.

Write a helper function that takes the model before your event and the model after and compares them. Let it return the properties that are changed, then you can assert that only those properties that you intended to update are in the return list. This sort of helper is portable, maintainable, and reusable

Checking model state is a valid application of a unit test.

user7396598
  • 1,269
  • 9
  • 6
  • Thanks for the suggestion. Checking that a model didn't change more than expected is not my only concern but with Serge suggestion this should indeed cover most cases. however a generic function using reflection to find every field, checked against a list of allowed-to-change parameters is not that straightforward and I hoped someone already did it for me. – Titan Feb 05 '18 at 09:24
  • See this answer: https://stackoverflow.com/questions/737151/how-to-get-the-list-of-properties-of-a-class It is a nice, easy to read/write way to spit out the properties of an object – user7396598 Feb 05 '18 at 15:26
0

This is only possible in referentially transparent languages such as Safe Haskell.