AnyString() as parameter for unit test
Asked Answered
E

4

7

I have to deal with a legacy application that has no tests. So before I begin refactoring I want to make sure everything works as it is.

Now imagine the following situation:

public SomeObject doSomething(final OtherObject x, final String something) {
    if(x == null) throw new RuntimeException("x may not be null!");
    ...
}

Now I want to test that null check, so to be sure it works and I don't lose it once I refactor.

So I did this

@Test(expected = RuntimeException.class)
public void ifOtherObjectIsNullExpectRuntimeException() {
    myTestObject.doSomething(null, "testString");
}

Now, this works of course.

But instead of "testString" I'd like to pass in a random String.

So I tried with:

@Test(expected = RuntimeException.class)
public void ifOtherObjectIsNullExpectRuntimeException() {
    myTestObject.doSomething(null, Mockito.anyString());
}

But this is not allowed., as I get

org.mockito.exceptions.misusing.InvalidUseOfMatchersException: ... You cannot use argument matchers outside of verifications or stubbing

I do understand the meaning of this, but I wonder whether I can still manage to do what I want without parameterizing my test or the like. The only libraries I may use are Junit, AssertJ, Mockito and Powermock.

Any ideas?

Eloiseelon answered 9/9, 2016 at 19:27 Comment(0)
Q
4

Well, like Mockito is trying to tell you via that exception, that's not really how you'd use anyString. Such methods are only to be used by mocks.

So, why not try testing with an actual random string? My personal favorite in such a scenario: java.util.UUID.randomUUID().toString(). This will virtually always generate a brand new string that has never been used for your test before.

I'd also like to add that if you are writing tests for your SomeObject class that you should avoid mocking SomeObject's behavior. From your example, you weren't exactly doing that, but it looked like you might be going down that route. Mock the dependencies of the implementation you're trying to test, not the implementation itself! This is very important; otherwise you aren't actually testing anything.

Quincyquindecagon answered 9/9, 2016 at 20:2 Comment(5)
That works for strings, but for e.g. Longs or other objects?Eloiseelon
Also, randomUUID() will never return null afaik, so that is a major drawback.Eloiseelon
@Sorona Please see java.util.Random. Everything you need for testing with random values can be found there. Also, if you want to test specifically for a null value, that should be a separate unit test. Unit tests are supposed to be cheap and fast; don't be afraid to add a new test case where you think it's necessary!Quincyquindecagon
And thanks, but of course I mock the implementation of the dependencies, e.g. when a service is called I do doReturn(false).when(dependencyService).isXValid(INVALID_VALID_X);Eloiseelon
I don't think it necessarily has to be a different (unit) test. Nulls are often something one has to deal with manually, that's right, so it's an edge-case. But sometimes the API says (implicitly) that something cannot be null, yet suddenly a wild null appears and breaks everything, because there is no unit-test, because "the api said it cannot be null, so why check for it?" (We had that the other day -.-) Hence I would like to automate more and more of the test-generation as well. Go BDD basically.Eloiseelon
P
6

Tests should be deterministic. Using random values in a test makes it difficult to reproduce behavior when debuging a failed test. I suggest that you just create a String constant for the test such as "abcdefg".

Phototelegraph answered 9/9, 2016 at 19:36 Comment(6)
I concur that many tests should be deterministic, but there is a reason parameterized tests exist. You don't always want to do TDD, but BDD as well, where the test suite tries all kinds of possible (and "impossible") values, just to see your tests fail. Personally I see the benefits of both views. If you only go for deterministic tests as in "tests the programmer thought about" you will not always be able to cover even the most basic real world scenarios.Eloiseelon
@Sorona Parameterized tests can certainly be deterministic.Phototelegraph
They can, yes, but they are not always. Well, actually if you wrap your head around the framework and randomness in computer science they are probably always "kind of deterministic" ;) Still, what I try to do here is make sure that no matter the second parameter, it should always fail. Hence I want to enhance my chances that if a certain object comes along it does not break my implementation. Tests will be running 24/7 basically, so statistically...Eloiseelon
@Sorona It sounds like you probably need to make this test parameterized. As you probably know, you should have a way to see the value of the random String in case there are problems that you need to debug.Phototelegraph
I can always log the actual values if the test failed ;)Eloiseelon
@Sorona Just be sure that it is displayed with the test output to save the extra step of searching the logcat.Phototelegraph
Q
4

Well, like Mockito is trying to tell you via that exception, that's not really how you'd use anyString. Such methods are only to be used by mocks.

So, why not try testing with an actual random string? My personal favorite in such a scenario: java.util.UUID.randomUUID().toString(). This will virtually always generate a brand new string that has never been used for your test before.

I'd also like to add that if you are writing tests for your SomeObject class that you should avoid mocking SomeObject's behavior. From your example, you weren't exactly doing that, but it looked like you might be going down that route. Mock the dependencies of the implementation you're trying to test, not the implementation itself! This is very important; otherwise you aren't actually testing anything.

Quincyquindecagon answered 9/9, 2016 at 20:2 Comment(5)
That works for strings, but for e.g. Longs or other objects?Eloiseelon
Also, randomUUID() will never return null afaik, so that is a major drawback.Eloiseelon
@Sorona Please see java.util.Random. Everything you need for testing with random values can be found there. Also, if you want to test specifically for a null value, that should be a separate unit test. Unit tests are supposed to be cheap and fast; don't be afraid to add a new test case where you think it's necessary!Quincyquindecagon
And thanks, but of course I mock the implementation of the dependencies, e.g. when a service is called I do doReturn(false).when(dependencyService).isXValid(INVALID_VALID_X);Eloiseelon
I don't think it necessarily has to be a different (unit) test. Nulls are often something one has to deal with manually, that's right, so it's an edge-case. But sometimes the API says (implicitly) that something cannot be null, yet suddenly a wild null appears and breaks everything, because there is no unit-test, because "the api said it cannot be null, so why check for it?" (We had that the other day -.-) Hence I would like to automate more and more of the test-generation as well. Go BDD basically.Eloiseelon
L
3

You are mixing up concepts here.

All those "mocking" helpers like anyString() are meant to be used when configuring a mock object.

But when you check your testing code:

@Test(expected = RuntimeException.class)
public void ifOtherObjectIsNullExpectRuntimeException() {
  myTestObject.doSomething(null, "testString");
}

you will find: there is absolutely no mocking involved for this test. You simply can't use those Mockito calls in that place; because "there is no Mockito" in that place.

And just for the record - no need to go overboard here anyway. Your logic is very clear here: when the first argument is null, then you throw that exception. Thus it really doesn't matter at all what comes in as second argument. So thinking for an hour how to test null with any second argument is, well, in my eyes: waste of your time.

Final hint: there is java.lang.Objects And that class has a nice check for null, so my production code only looks like

public SomeObject doSomething(final OtherObject x, final String something) {
  Objects.requireNonNull(otherObject, "otherObject must not be null");
  Objects.requireNonNull(something, "something must not be null");

Only difference there: requires... throws NullPointerExceptions

Final finally: some people suggest to put final on every parameter; but I wouldn't do that. It adds no value in 99% of all cases. It just means that you have more code to read; for no good reasons. But that is a question of style.

EDIT on the comment about having a test to check for potential future changes: you shouldn't do that:

  1. To a certain degree, how your input is verified is an implementation detail. You don't test for implementation details. In other words:
  2. Your method has a certain contract (that you, for example specify informally by writing a javadoc that says "throws NPE on null input"). Your tests should verify exactly that current contract. And the contract is: throws if first argument is null.

And maybe another point of view; as I still think you are wasting your time here! You should make sure that all your interfaces are clear, easy to understand, and easy to use. That they allow users of your code to do the right thing easily; and prevent him from doing wrong things. That is what you should focus on - the quality of your interfaces as a whole! So instead of worrying how you could write a test for potential future changes; just make sure that your code base is overall consistent.

Lavellelaven answered 10/9, 2016 at 4:33 Comment(1)
The thing is, I do agree with you. But it really happened that people changed the code so that it was throwing the exception if(a && !b) instead of if(a)... I would like a way to have a test that ensures that only the first argument is used in that check.Eloiseelon
V
0

Well i do not have much knowledge of mockito but you can always create your own random string generator. maybe that can work and u can modify more types of inputs in it

Vo answered 9/9, 2016 at 19:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.