5

Given a class hierarchy that looks like this:

public class Vehicle {

    private String name;

    public Vehicle(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

}


public class Car extends Vehicle {

    public Car(String name) {
        super(name);
    }

    public String drive() {
        return "driving the car";
    }

    public String boardBus() {
        Bus bus = new Bus("bus to cut off");

        return bus.board();
    }

}

public class Bus extends Vehicle {

    public Bus(String name) {
        super(name);
    }

    public String board() {
        return "boarding the bus";
    }

}

I'm trying to test the Car class. However, Car also happens to make use of Bus. So, in my test, I'm trying to mock Bus. My test code looks like this:

import static org.junit.Assert.assertEquals;
import mockit.Mocked;
import mockit.NonStrictExpectations;

import org.junit.Test;

public class CarTest {

    @Test
    public void testCar() {
        final String name = "I am a car";
        final Car car = new Car(name);

        new NonStrictExpectations() {
            @Mocked Bus bus;

            {
                bus.board(); result = "test bus boarding";
            }
        };

        assertEquals("I am a car", car.getName());
    }

}

The assert fails because car.getName() returns null.

By inserting System.out.println's in the constructors for Vehicle, Car, and Bus, I'm suspecting that the "real" Vehicle that is loaded by new Car(name) is later replaced by a mocked Vehicle when the @Mocked Bus bus is executed.

Is there a way for jmockit to preserve the real Vehicle that is "instantiated" when Car is constructed?

rdguam
  • 356
  • 5
  • 10

2 Answers2

1

I see two solutions:

@Test
public void boardBus_usingInstanceSpecificMockingForNewedInstances()
{
    new Expectations() {
        @Capturing @Injectable Bus bus;

        {
            bus.board(); result = "mocked";
        }
    };

    String result = new Car("myCar").boardBus();
    assertEquals("mocked", result);
}

@Test
public void boardBus_usingPartialMocking()
{
    final Bus bus = new Bus("");
    new Expectations(bus) {{ bus.board(); result = "mocked"; }};

    String result = new Car("myCar").boardBus();
    assertEquals("mocked", result);
}
Rogério
  • 16,171
  • 2
  • 50
  • 63
0

There is no Vehicle "associated" with a Car - Car is a Vehicle. Association and inheritance are not the same. Therefore it's not possible to have a Car with a "mocked" Vehicle - this sentence is meaningless when Car extends Vehicle.

Are you sure there isn't a (legit) bug in either the Car's constructor or getName()? What does the code for these methods look like?

matt b
  • 138,234
  • 66
  • 282
  • 345
  • 1
    and what if you remove the `new NonStrictExpectations() { @Mocked ...` stuff from the unit test, to just purely test the Car constructor and `getName()`? Does a test without any mocking pass? Something doesn't add up here. – matt b Jul 20 '11 at 15:27
  • Yes, the test passes if I comment out the `new NonStrictExpectations() { ... };`. – rdguam Jul 20 '11 at 15:39
  • Addendum: My understanding of jmockit is that it relies on classes rather than on objects, and it will automatically mock a class's parents all the way up to, but not including, java.lang.Object. So when it sees `@Mocked` on a class, it will mock that class and its parents. What I'm hoping to find out is how to isolate that behavior only to that particular class that's being mocked, so as not to affect its sibling classes in the hierarchy. – rdguam Jul 20 '11 at 15:45