0

I want to set the value 'ignore' to both true and false. Currently already able to set it to true at @Before. But how could I also test by setting it to false. Note that I need this to be a constructor initialization.

Setting the value via ReflectionTestUtils not going to work due to the value being set in constructor. I could call the constructor again and set the value to false but that would involve a lot of setups in this test class with all the relevant mocks and so on which would get messy. Is there a way around this?

I have the following constructor

// many other variables not relevant for this question
private final boolean ignore;

public Client(@Value("${a.error}") boolean ignore) {
    // setting many other variables not relevant for this question
    this.ignore = ignore;
}

When testing:

@Before
public void setUp() {
    client = new Client(true);
    //many other setups
}

// tests correctly fine cos I set the ignore to true
@Test
public void testing(){
    // someMethod uses the ignore value to do some actions and return true / false
    assertTrue(client.someMethod());
}

@Test
public void howToTestIgnoreSetToFalse(){
    // ? 
}
karvai
  • 2,417
  • 16
  • 31
  • Create two instances, the first with `new Client(true)` and the second with `new Client(false)`. – Roland Weisleder Mar 11 '20 at 10:08
  • what about `new Client(false)`? – Nicktar Mar 11 '20 at 10:09
  • @RolandWeisleder As mentioned in the question, yes that is one option but it breaks some other setups I have. I am trying not to do that. – karvai Mar 11 '20 at 10:11
  • @Nicktar Same answer as my above comment. – karvai Mar 11 '20 at 10:11
  • 1
    Ditch the `setUp` method and just construct the `Client` inside your test method where you needed. Or if the `Client` is needed for further setup. Make it a factory method, and call that from your test. – M. Deinum Mar 11 '20 at 10:11
  • @M.Deinum there are over 30 test cases in this test class. If I ditch setup, I would need to call a constructor (which has 8 params) for every single test. Don't find that to be very ideal. – karvai Mar 11 '20 at 10:13
  • @Nicktar This is some legacy code. Code smell sure but it is going out of scope trying to fix that. What I am trying to achieve here, to write a decent test when the value is false. – karvai Mar 11 '20 at 10:20
  • @karvai If you don't want to fix any issues but also don't want to live with them, you might be short on other options apart from creating a second test class to host a setup that initializes the parameter to false and all the tests concerning that use case. – Nicktar Mar 11 '20 at 10:42
  • 1
    Does this answer your question? [Populating Spring @Value during Unit Test](https://stackoverflow.com/questions/17353327/populating-spring-value-during-unit-test) – Abder KRIMA Mar 11 '20 at 12:02

2 Answers2

0

I can suggest 3 solutions here:

  1. Using ReflectionUtils by Spring
@Before
public void setUp() {
    client = new Client(true);
    // rest of initialization
}

@Test
public void testing(){
    assertTrue(client.someMethod());
}

@Test
public void howToTestIgnoreSetToFalse(){
    Field fieldIgnore = Client.class.getDeclaredField("ignore");
    ReflectionUtils.makeAccessible(fieldIgnore);
    ReflectionUtils.setField(fieldIgnore, client, false);

    assertFalse(client.someMethod());
}
  1. Using default Reflection API
@Test
public void howToTestIgnoreSetToFalse(){
    Field fieldIgnore = Client.class.getDeclaredField("ignore");
    // only the way of how you're initializing field is changed,
    // everything else is the same
    fieldIgnore.setAccessible(true);
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL);
    fieldIgnore.set(client, false);

    assertFalse(client.someMethod());
}
  1. Extract initialization to separate method
// setup method doesn't required anymore

@Test
public void testing(){
    Client client = createClient(true);
    assertTrue(client.someMethod());
}

@Test
public void howToTestIgnoreSetToFalse(){
    Client client = createClient(false);
    assertTrue(client.someMethod());
}

// factory method to prepare mocked/initialized instance
private static Client createClient(boolean ignore) {
    Client client = new Client(ignore);
    // do common initialization
    // setup your mocks
    return client;  
}

Hope it helps!

Sergey Prokofiev
  • 1,815
  • 1
  • 12
  • 21
0

This is such a common issue, we developed a Junit5 extension that supports @InjectMocks to inject arbitrary fields. This allows one to inject @Config when testing Spring code or many other scenarios.

https://github.com/exabrial/mockito-object-injection

To use:

<dependency>
 <groupId>com.github.exabrial</groupId>
 <artifactId>mockito-object-injection</artifactId>
 <version>1.0.4</version>
 <scope>test</scope>
</dependency>

And here's an example of injecting a Boolean (which can't be mocked with Mockito)

@TestInstance(Lifecycle.PER_METHOD)
@ExtendWith({ MockitoExtension.class, InjectMapExtension.class })
public class MyControllerTest {
 @InjectMocks
 private MyController myController;
 @Mock
 private Logger log;
 @Mock
 private Authenticator auther;
 @InjectionMap
 private Map<String, Object> injectionMap = new HashMap<>();

 @BeforeEach
 public void beforeEach() throws Exception {
  injectionMap.put("securityEnabled", Boolean.TRUE);
 }

 @AfterEach
 public void afterEach() throws Exception {
  injectionMap.clear();
 }

 public void testDoSomething_secEnabled() throws Exception {
  myController.doSomething();
  // wahoo no NPE! Test the "if then" half of the branch
 }

 public void testDoSomething_secDisabled() throws Exception {
  injectionMap.put("securityEnabled", Boolean.FALSE);
  myController.doSomething();
  // wahoo no NPE! Test the "if else" half of branch
 }
}
Jonathan S. Fisher
  • 8,189
  • 6
  • 46
  • 84