38

I started writing some unit tests for my current project. I don't really have experience with it though. I first want to completely "get it", so I am currently using neither my IoC framework nor a mocking library.

I was wondering if there is anything wrong with providing null arguments to objects' constructors in unit tests. Let me provide some example code:

public class CarRadio
{...}


public class Motor
{
    public void SetSpeed(float speed){...}
}


public class Car
{
    public Car(CarRadio carRadio, Motor motor){...}
}


public class SpeedLimit
{
    public bool IsViolatedBy(Car car){...}
}

Yet Another Car Code Example(TM), reduced to only the parts important to the question. I now wrote a test something like this:

public class SpeedLimitTest
{
    public void TestSpeedLimit()
    {
        Motor motor = new Motor();
        motor.SetSpeed(10f);
        Car car = new Car(null, motor);

        SpeedLimit speedLimit = new SpeedLimit();
        Assert.IsTrue(speedLimit.IsViolatedBy(car));
    }
}

The test runs fine. SpeedLimit needs a Car with a Motor in order to do its thing. It is not interested in a CarRadio at all, so I provided null for that.

I am wondering if an object providing correct functionality without being fully constructed is a violation of SRP or a code smell. I have this nagging feeling that it does, but speedLimit.IsViolatedBy(motor) doesn't feel right either - a speed limit is violated by a car, not a motor. Maybe I just need a different perspective for unit tests vs. working code, because the whole intention is to test only a part of the whole.

Is constructing objects with null in unit tests a code smell?

jonrsharpe
  • 1,323
R. Schmitz
  • 2,608
  • 24
    A bit off-topic, but Motor probably shouldn't have a speed at all. It should have a throttle and compute a torque based on the current rpm and throttle. It's the car's job to use a Transmission to integrate that into a current speed, and to turn that into an rpm to signal back to the Motor... But I guess, you weren't in it for the realism anyway, were you? – cmaster - reinstate monica Mar 20 '18 at 14:34
  • 17
    The code smell is that your constructor takes null without complaining in the first place. If you think this is acceptable (you might have your reasons for this): go ahead, test it! – Tommy Mar 20 '18 at 16:02
  • If it's not important, what CarRadio there is, you could simply create a dummy constant containing a default CarRadio and use that in your test. That way, you'll avoid using null. – QBrute Mar 20 '18 at 16:44
  • 4
    Consider the Null-Object pattern. It often simplifies code, while also making it NPE-safe. – Bakuriu Mar 20 '18 at 18:21
  • @cmaster Are you sure you didn't want to use a lot of instances of an Atom class, set up and aligned to form a car? :D But no, the actual thing wasn't even about cars at all. – R. Schmitz Mar 20 '18 at 18:45
  • 1
    @QBrute & Bakuriu That's just null with extra steps. The question is more if the null is a symptom for something else which I would need to improve. – R. Schmitz Mar 20 '18 at 18:49
  • @R.Schmitz It's a null with the non-negligible difference that the rest of your code doesn't have to be full of checks for "is it null?". And I suspect many would say that, if your code is not intended to work with a null, passing a null is a code smell at best, but that passing a "null object" in such a case is a reasonable basic form of mocking. – mtraceur Mar 21 '18 at 01:15
  • 1
    @Tommy I think it's perfectly reasonable. For this example, not all cars have radios -- sure, your consumer SUV might, but what about that military Humvee? Or a Formula 1 racecar? (To get back to the point -- it's absolutely not a code smell that a constructor can take null. Sometimes you have optional things; that's null's entire point.) –  Mar 21 '18 at 04:47
  • 17
    Congratulations: you have tested that with a null radio, the speed limit is correctly computed. Now you may want to create a test to validate the speed limit with a radio; just in case the behavior differs... – Matthieu M. Mar 21 '18 at 08:16
  • 1
    @Nic Hartley Looking at the Car-ctor, there is no way to find out that a radio is less important than a motor to a car (both can be null, can't they?). And that's the code smell. Well, there are tools like method (ctor) overloading, factory methods, even inheritance for a reason.There is no need to carry around the null. That the entire point of null is representing optional things is your interpretation. The guys who added Optional to the standard library might not agree. – Tommy Mar 21 '18 at 09:56
  • 4
    Not sure about this example, but sometimes the fact that you only want to test half a class is a clue that something should be broken up – Owen Mar 21 '18 at 14:08
  • 1
    @owen indeed. To use null dependencies is legit as soon as we want to tests intendedly against nulls. The OP's example is ok, but it"s showing up a design flaw. While the tests is ok, the class Car suffers of code smell. That's why I love testing. It makea you swift the mindset from developer (designer) to consumer :-) – Laiv Mar 21 '18 at 17:43
  • @Tommy I... still disagree. In this case, it's pretty obvious that the class is meant to model a real-world object (namely, a car). Cars don't always have radios. That's part of the minimum domain knowledge to be able to design this code effectively. It's very much a case-by-case basis thing, and where it's not immediately obvious (such as, say, whether or not a car needs a radio vs. a motor to function) it should be documented. In this specific case, though, it is pretty obvious, so it's not really necessary. –  Mar 21 '18 at 20:30

7 Answers7

70

In the case of the example above, it is reasonable that a Car can exist without a CarRadio. In which case, I'd say that not only is it acceptable to pass in a null CarRadio sometimes, I'd say that it's obligatory. Your tests need to ensure that the implementation of the Car class is robust, and does not throw null pointer exceptions when no CarRadio is present.

However, let's take a different example - let's consider a SteeringWheel. Let's assume that a Car has to have a SteeringWheel, but the speed test doesn't really care about it. In this case, I wouldn't pass a null SteeringWheel as this is then pushing the Car class into places where it isn't designed to go. In this case, you'd be better off creating some sort of DefaultSteeringWheel which (to continue the metaphor) is locked in a straight line.

Pete
  • 3,211
  • @DavidArno I'd say that this answer is close to Eternal's answer, boiling down to using a different, dedicated testing class for tests. Your answer makes a completely different, definitely valid point about the robustness of the tests and I upvoted it for that very reason. (Fake internet points aside, all three answers have been helpful in their own way so far.) – R. Schmitz Mar 20 '18 at 12:51
  • 44
    And don't forget to write a unit test to check that a Car cannot be constructed without a SteeringWheel... – cmaster - reinstate monica Mar 20 '18 at 14:28
  • 13
    I might be missing something, but if it's possible and even common to create a Car without a CarRadio, wouldn't it make sense to create a constructor that doesn't require one to be passed in and not have to worry about an explicit null at all? – an earwig Mar 20 '18 at 16:34
  • 16
    Self driving cars might not have a steering wheel. Just saying. ☺ – BlackJack Mar 20 '18 at 16:54
  • 1
    @James_Parsons I forgot to add that it was about a class which didn't have null checks because it was only used internally and always received non-null parameters. Other methods of Car would have thrown an NRE with a null CarRadio, but the relevant code for SpeedLimit wouldn't. In the case of the question as posted now you would be correct and I would indeed do that. – R. Schmitz Mar 20 '18 at 18:40
  • @R.Schmitz Please edit your question to add the fact that your question "was about a class which didn't have null checks because it was only used internally and always received non-null parameters. Other methods of Car would have thrown an NRE with a null CarRadio, but the relevant code for SpeedLimit wouldn't." This information is substantially relevant to answering your question, because many people would say that the answer changes depending on this piece of information. – mtraceur Mar 21 '18 at 01:22
  • @BlackJack They have to be steered. SO the name of the class might be a bit off, but not its functionality. – glglgl Mar 21 '18 at 08:25
  • 2
    @mtraceur The way everybody assumed that the class allowing construction with a null argument means null is a valid option made me realize that I should probably have null checks there. The actual issue I would have then is covered by a lot of good, interesting, well-written answers here which I don't want to ruin and which did help me. Also accepting this answer now because I don't like to leave things unfinished and it's the most upvoted (although they all were helpful). – R. Schmitz Mar 21 '18 at 11:19
  • @R.Schmitz Understandable, and I agree: The question and answers are valuable on their own as written. Sidenote: I think this is a valuable look into how humans interface with software: If the code doesn't immediately error out on given inputs, lots (most?) people assume that the code is within correct/intended usage. (Or put in other words, the only documentation of your interface that other developers are guaranteed to actually notice are in the form of errors raised from the code itself.) – mtraceur Mar 21 '18 at 19:25
24

Is constructing objects with null in unit tests a code smell?

Yes. Note the C2 definition of code smell: "a CodeSmell is a hint that something might be wrong, not a certainty."

The test runs fine. SpeedLimit needs a Car with a Motor in order to do its thing. It is not interested in a CarRadio at all, so I provided null for that.

If you are trying to demonstrate that speedLimit.IsViolatedBy(car) does not depend on the CarRadio argument passed to the constructor, then it would probably be clearer to the future maintainer to make that constraint explicit by introducing a test double that fails the test if it is invoked.

If, as is more likely, you are just trying to save yourself the work of creating a CarRadio that you know isn't used in this case, you should notice that

  • this is a violation of encapsulation (you are letting the fact that CarRadio isn't used leak out into the test)
  • you have discovered at least one case where you want to create a Car without specifying a CarRadio

I strongly suspect that the code is trying to tell you that you want a constructor that looks like

public Car(IMotor motor) {...}

and then that constructor can decide what to do with the fact that there is no CarRadio

public Car(IMotor motor) {
    this(null, motor);
}

or

public Car(IMotor motor) {
    this( new ICarRadio() {...} , motor);
}

or

public Car(IMotor motor) {
    this( new DefaultRadio(), motor);
}

etc.

Its about if passing null to something else while testing SpeedLimit. You can see that the test is called "SpeedLimitTest" and the only Assert is checking a method of SpeedLimit.

That's a second interesting smell - in order to test SpeedLimit, you have to build an entire car around a radio, even though the only thing that the SpeedLimit check probably cares about is the speed.

This might be a hint that SpeedLimit.isViolatedBy should be accepting a role interface that car implements, rather than requiring an entire car.

interface IHaveSpeed {
    float speed();
}

class Car implements IHaveSpeed {...}

IHaveSpeed is a really lousy name; hopefully your domain language will have an improvement.

With that interface in place, your test for SpeedLimit could be much simpler

public void TestSpeedLimit()
{
    IHaveSpeed somethingTravelingQuickly = new IHaveSpeed {
        float speed() { return 10f; }
    }

    SpeedLimit speedLimit = new SpeedLimit();
    Assert.IsTrue(speedLimit.IsViolatedBy(somethingTravelingQuickly));
}
  • In your last example I presume you'd have to create a mock class that implements the IHaveSpeed interface as (at least in c#) you wouldn't be able to instantiate an interface itself. – Ryan Searle Mar 22 '18 at 20:16
  • 1
    That's right - the effective spelling will depend upon which flavor of Blub you are using for your tests. – VoiceOfUnreason Mar 22 '18 at 20:25
18

Is constructing objects with null in unit tests a code smell?

No

Not at all. To the contrary, if NULL is a value that is available as an argument (some languages may have constructs that disallow passing of a null pointer), you should test it.

Another question is what happens when you pass NULL. But that is exactly what a unit test is about: making sure a defined result is happening when for each possible input. And NULL is a potential input, so you should have a defined result and you should have a test for that.

So if your Car is supposed to be constructable without a radio, you should write a test for that. If it's not and is supposed to throw an exception, you should write a test for that.

Either way, yes you should write a test for NULL. It would be a code smell if you left it out. That would mean you have an untested branch of code that you just assumed would magically be bug-free.


Please note that I do agree with the other answers that your code under test could be improved. Nonetheless, if the improved code has parameters that are pointers, passing NULL even for the improved code is a good test. I would want a test to show what happens when I pass NULL as a motor. That's not a code smell, that's the opposite of a code smell. It's testing.

nvoigt
  • 7,737
  • 3
  • 23
  • 27
  • It seems like you are writing bout testing Car here. While I don't see anything I'd consider a wrong statement, the question is about testing SpeedLimit. – R. Schmitz Mar 20 '18 at 18:32
  • @R.Schmitz Not sure what you mean. I have quoted the question, it's about passing NULL to the constructor of Car. – nvoigt Mar 21 '18 at 15:39
  • 1
    Its about if passing null to something else while testing SpeedLimit. You can see that the test is called "SpeedLimitTest" and the only Assert is checking a method of SpeedLimit. Testing Car - for example "if your Car is supposed to be constructable without a radio, you should write a test for that" - would happen in a different test. – R. Schmitz Mar 21 '18 at 16:15
7

I personally would be hesitant to inject nulls. In fact, whenever I inject dependencies into a constructor, one of the first things I do is raise an ArgumentNullException if those injections are null. That way I can safely use them inside my class, without dozens of null-checks spread around. Here's a very common pattern. Note the use of readonly to ensure those dependencies set in constructor can not be set to null (or anything else) afterwards:

class Car
{
   private readonly ICarRadio _carRadio;
   private readonly IMotor _motor;

   public Car(ICarRadio carRadio, IMotor motor)
   {
      if (carRadio == null)
         throw new ArgumentNullException(nameof(carRadio));

      if (motor == null)
         throw new ArgumentNullException(nameof(motor));

      _carRadio = carRadio;
      _motor = motor;
   }
}

With the above, I could perhaps have a unit test or two, where I inject nulls, just to make sure my null checks work as expected.

Having said that, this becomes a non-issue, once you do two things:

  1. Program to interfaces instead of classes.
  2. Use a mocking framework (like Moq for C#) to inject mocked dependencies instead of nulls.

So for your example you'd have:

interface ICarRadio
{
   bool Tune(float frequency);
}

interface Motor
{
   bool SetSpeed(float speed);
}

...
var car = new Car(new Moq<ICarRadio>().Object, new Moq<IMotor>().Object);

More details on using Moq can be found here.

Of course, if you want to understand it without mocking first, you can still do it, as long as you are using interfaces. Simply create a dummy CarRadio and Motor as following, and inject those instead:

class DummyCarRadio : ICarRadio
{
   ...
}
class DummyMotor : IMotor
{
   ...
}

...
var car = new Car(new DummyCarRadio(), new DummyMotor())
Eternal21
  • 1,584
  • 3
    This is the answer I would have written – Liath Mar 20 '18 at 14:17
  • 1
    I would go even so far to say that the dummy objects also have to fulfill their contract. If IMotor had a getSpeed() method, the DummyMotor would need to return a modified speed after a successful setSpeed as well. – ComFreek Mar 20 '18 at 14:41
1

Let's take a step back to first principles.

A unit test tests some aspect of a single class.

There will however be constructors or methods that take other classes as parameters. How you handle these will vary from case to case but setup should be minimal since they're tangential to the objective. There may be scenarios where passing a NULL parameter is perfectly valid - such as a car having no radio.

I'm assuming here, that we're just testing the racing line to start with (to continue the car theme), but you may also want to pass NULL to other parameters to check that any defensive code and exception handing mechanisms are functioning as required.

So far so good. However, what if NULL isn't a valid value. Well, here you're into the murky world of mocks, stubs, dummies and fakes.

If all this seems a bit much to take on, you're actually already using a dummy by passing NULL in the Car constructor since it a value that potentially won't be used.

What should become clear pretty quickly, is that you're potentially shotgunning your code with a lot of NULL handling. If you replace class parameters with interfaces, then you also pave the way for mocking and DI etc which make these far more straightforward to handle.

Robbie Dee
  • 9,815
  • 2
  • 24
  • 53
  • 1
    "Unit tests are for testing the code of a single class." Sigh. That is not what unit tests are and following that guideline is either going to lead you into bloated classes or counterproductive, hindering tests. – Ant P Mar 21 '18 at 22:32
  • @Ant Each unit test tests some aspect of a single class - that is testing 101...isn't it...? – Robbie Dee Mar 21 '18 at 22:35
  • 3
    treating "unit" as a euphemism for "class" is unfortunately a very common way of thinking but in reality all this does is (a) encourage "garbage-in, garbage-out" tests, which can totally undermine the validity of entire test suites and (b) enforce boundaries that should be free to change, which can cripple productivity. I wish more people would listen to their pain and challenge this preconception. – Ant P Mar 21 '18 at 22:40
  • @AntP I think you're confusing unit tests with other sorts of testing. Unit testing tests code - nothing more. If you're testing some specific function through various layers, that is integration testing or whatever your shop calls it. I'm not saying there isn't another way to do unit testing but having tests in the same basic structure as the CUT just makes things so much easier to deal with rather than wading thru a morass of modules or relying on coverage tools. – Robbie Dee Mar 21 '18 at 22:43
  • 3
    No such confusion. Test units of behaviour, not units of code. There is nothing about the concept of a unit test - except in the widespread cargo cult hive mind of software testing - that implies that a test unit is coupled to the distinctly OOP concept of a class. And a very damaging misconception that is, too. – Ant P Mar 21 '18 at 22:48
  • @AntP There is nothing that binds it but equally dangerous is the belief that you have to build brundlefly objects to get tests to pass. Concentrate on making the method or whatever you're writing pass the test (red, green, refactor). Stop getting concerned with layers and code that hasn't (or shouldn't have!) been written yet. – Robbie Dee Mar 21 '18 at 22:52
  • 1
    I'm not sure precisely what you mean - I'm arguing the exact opposite of what you're implying. Stub/mock hard boundaries (if you absolutely have to) and test a behavioural unit. Through the public API. What that API is depends on what you're building but the footprint should be minimal. Adding abstraction, polymorphism or restructuring code should not cause tests to fail - "unit per class" contradicts this and utterly undermines the value of the tests in the first place. – Ant P Mar 21 '18 at 22:54
  • Yes, a single unit test will test some specific behaviour in a GWT style (or whatever). It just happens to sit in a test class that mirrors the CUT - YMMV. I'm increasingly getting the feeling that you're just talking about unit testing in very broad strokes rather than the more disciplined TDD approach. Vive la différence... – Robbie Dee Mar 21 '18 at 23:08
  • 1
    RobbieDee - I'm talking about the precise opposite. Consider that you may be the one harbouring common misconceptions, revisit what you consider a "unit" and maybe dig a little deeper into what Kent Beck was actually really talking about when he talked about TDD. What most people call TDD now is a cargo cult monstrosity. https://www.youtube.com/watch?v=EZ05e7EMOLM – Ant P Mar 22 '18 at 08:29
  • I'd advise you to forget what you think you know and concentrate on results. On our current multi-billion dollar project, we have user stories which drive GWT tests which drive up the quality of the code visibly. If you have something else working - great. But please don't be blinkered into thinking there is one way forevermore - you risk joining a cult of your own. – Robbie Dee Mar 22 '18 at 08:43
  • 1
    Robbie Dee - Ditto that. Up until recently I had followed the "unit test per class" mantra. I gradually realised that it creates a lot of manual assumptions about how classes interact and creates brittle tests that restrict change and are of little value when that change is made. Test boundaries enforce hard boundaries in code that are difficult to change and should be reserved for things that change infrequently - boundaries between classes shouldn't face this restriction. Unit testing code in isolation - great - but isolate slices of behaviour rather than coupling to class boundaries. – Ant P Mar 22 '18 at 10:53
  • I'm with @AntP on this one, read the excellent book The Art of Unit Testing and that's one of the main points of the book is that "unit" does NOT mean "class", it means "unit of behavior". A "unit" could be as big as a dll or service – reggaeguitar Mar 22 '18 at 14:28
  • @reggaeguitar You too are obsessing about where the code sits - the same API is being tested whether it sits in a test class mirroring the CUT or arranged along any other lines. The tests are exactly the same. I could see how your argument with have traction if the classes were arranged in some random arbitrary manner but in reality, it is a bagatelle. – Robbie Dee Mar 22 '18 at 15:04
  • And just to pin my colours to the mast, if you subscribe to a similarly skewed view of what a unit test is - you are categorically wrong. Period. Sorry to be blunt on this, but this site caters for students and newbies as well as seasoned professionals. Sound knowledge is hard enough to come by without people deliberately muddying the waters. Don't take an accepted term and bastardise it for your own questionable ends. It is lazy and unprofessional. – Robbie Dee Mar 22 '18 at 17:34
  • @RobbieDee I completely agree with that article, it seems to be agreeing with AntP and myself, not sure what I'm missing here. I'm just saying that a "unit" isn't necessarily a class – reggaeguitar Mar 23 '18 at 14:21
  • @reggaeguitar Read the OP again. We are dealing with classes here. But yes, in theory, unit testing can apply to all sorts of languages which may not have a class construct. – Robbie Dee Mar 23 '18 at 14:25
1

It’s not just okay, it’s a well known dependency breaking technique for getting legacy code into a test harness. (See “Working Effectively with Legacy Code” by Michael Feathers.)

Does it smell? Well...

The test doesn’t smell. The test is doing its job. It’s declaring “this object can be null and the code under test still works correctly”.

The smell is in the implementation. By declaring a constructor argument, you’re declaring “this class needs this thing in order to properly function”. Obviously, that’s untrue. Anything untrue is a bit smelly.

RubberDuck
  • 8,961
  • 5
  • 35
  • 44
1

Taking a look at your design, I would suggest that a Car is composed of a set of Features. A Feature maybe a CarRadio, or EntertainmentSystem which may consist of things like a CarRadio, DvdPlayer etc.

So, at a minimum, you would have to construct a Car with a set of Features. EntertainmentSystem and CarRadio would be implementers of the interface (Java, C# etc) of the public [I|i]nterface Feature and this would be an instance of the Composite design pattern.

Therefore, you would always construct a Car with Features and a unit test would never instantiate a Car without any Features. Although you might consider mocking a BasicTestCarFeatureSet that has a minimal set of functionality required for your most basic test.

Just some thoughts.

John

johnd27
  • 11