I would like to Unit Test my Kotlin class, which makes a POST request using micronaut's RxHttpClient.
The code I want to test (simplified):
fun makePost() {
val httpRequest = HttpRequest.POST<Any>(uri, json).contentType(MediaType.APPLICATION_JSON)
return httpClient.exchange(httpRequest, String::class.java)
.singleOrError()
.map { response ->
println("Success")
}.doOnError { error ->
println("Error"))
}
}
I don't want it to hit the actual url of the post, I'm looking for a way to "mock" the response.
I already tried to use mock-server and mocking the HttpClient using mockito, without success.
Can someone help me, or give me any advice about what should I do?
Thanks
Related
I have been mulling on ways to end-to-end test an API call, with the likes of POST/PUT/DELETE troubling me the most.
The reason being, nunits don't run in order - and if they are asked to explicitly, they do not necessary wait for the previous test to finish. So how does one test against test data? e.g.
Post a testdata json
Put a testdata update
Get the testdata
Delete the testdata
so that by the end of my test I have not polluted my DB, and I have made sure all my endpoints provide with end to end functionality?
Between injecting the actual repository to set up and tear down DB entries, and what not, in the end I decided on:
[Test]
public void PostAndPutAndDeleteWorkWithoutErrors()
{
//setup
var client = JsonClient.GetClient();
var request = GenerateNewRequest();
//act post
var httpResponse = client.PostJson(uri, request );
//assert
httpResponse.StatusCode.ShouldBe(HttpStatusCode.OK);
//act put
httpResponse = client.PutJson(uri, request );
//assert
httpResponse.StatusCode.ShouldBe(HttpStatusCode.OK);
//act delete
httpResponse = client.DeleteJson(uri, request .Name);
//assert
httpResponse.StatusCode.ShouldBe(HttpStatusCode.OK);
}
Now this test tests more things than one (Post/Put/Delete), which breaks the idea of a single unit of work...
Or could one say : this does test a single unit of work - the entire cycle of posting, putting, and deleting.
Is there a better way to do this?
Thanks
So Ive got a Grails 3.2.3 app, and in one of the services, I am loading a WSDL located in the root of my project folder to use with a SOAPClient like so:
def wsdl = "soap.wsdl"
SOAPClient soapClient
#PostConstruct
void init() {
soapClient = new SOAPClient(wsdl)
}
My spock test is annotated with #MockFor(MyService) and Ive checked that the test runs the init method fine.
Now when the app is running, this creates the SOAPClient just fine, but when trying to write a Spock test, new SOAPClient(wsdl)returns null. Im guessing this is because when running the Spock test, the wsdl isnt properly in the classpath, but I havent found a way to solve this. Any ideas please?
If it were me, I would metaClass the constructor of SOAPClient to return a mock of SOAPClient. Something like this:
def 'test something'(){
setup:
def soapClientMock = Mock(SOAPClient)
SOAPClient.metaclass.constructor = {String filename ->
assert filename == "soap.wsdl"
return soapClientMock
}
}
I Have looked at many questions along the same line of thought here on stack overflow, and else where but unable to find a solution to this particular issue.
I'm fairly new to Unit Testing in general, so the mistake may be (hopefully) obvious to someone with more experience.
Here's the issue:
I have a ResourceController that injects a class into the constructor using Depedency Injection.
public function __construct(ResourceAPIInterface $api)
{
$this->api = $api;
}
When that API is called in the controller, the class that was injected does some business logic and returns an Eloquent Collection.
public function index($resource, $version)
{
$input = Input::all();
//Populate Data
$data = $this->api->fetchAll($input);
//Format response
if($data->isEmpty()){
//Format response
$response = Response::make(" ", 204);
}else {
//Format response
$response = Response::make($data, 200);
}
//Set content-type in header
$response->header('Content-Type', 'application/json');
$response->header('Cache-Control', 'max-age=3600');
return $response;
}
As you can see from the code above, I need the response to be an eloquent response so i can test to see if it's empty. The method FetchAll literally just returns a Eloquent collation of all records in the table. When I do the test, i'm able to mock the API without issue. However when i'm mocking the response, i really want the response to be an eloquent collection, and having difficulty getting that to work. Here's an example of the test:
$course = Mockery::mock(new API\Entity\v1\Test);
$this->mock->shouldReceive('fetchAll')->once()->andReturn($course->all());
$this->mock->shouldReceive('name')->once()->andReturn('Course');
// Act...
$response = $this->action('GET', 'ResourceController#show');
// Assert...
$this->assertResponseOk();
The above works, but when i want to do the same test against the show method and mock the eloquent response for ->first() I'm getting errors.
1) ResourceControllerTest::testshow
BadMethodCallException: Method Mockery_1_API_Entity_v1_Test_API_Entity_v1_Test::first() does not exist on this mock object
I've tried to test the model by doing:
$course = Mockery::mock('Eloquent', 'API\Entity\v1\Test');
$response = $course->mock->shouldReceive('find')->with(1)->once()->andReturn((object)array('id'=>1, 'name'=>'Widget-name','description'=>'Widget description'));
However when I run that in the Test I get the following error:
1) ResourceControllerTest::testIndex
BadMethodCallException: Method Mockery_1_API_Entity_v1_Test::getAttribute() does not exist on this mock object
Any Ideas on how to resolve this issue? Also, if there's a better way to test if the eloquent collection is empty that might resolve some of the complexity that I'm running into is also welcome.
Ok, I figured out how to make this work:
public function testIndex($resource="course", $version="v1")
{
// Arrange...
$course = Mockery::mock('Eloquent', 'API\Entity\v1\Page')->makePartial();
$course->shouldReceive('isEmpty')->once()->andReturn(false);
$course->shouldReceive('all')->once()->andReturn($course);
$this->mock->shouldReceive('fetchAll')->once()->andReturn($course->all());
$this->mock->shouldReceive('name')->once()->andReturn('Course');
// Act...
$response = $this->action('GET', 'ResourceController#index');
// Assert...
$this->assertResponseOk();
}
I was able to do the PartialMock to get around the getAttribute() Error. Once I did that, I started getting the error:
Call to undefined method stdClass::isEmpty()
So I decided to mock that as well, and pass the whole mocked object into the expected response for the all command.
Then in the mock for the API class $this->mock-> i had it return The mocked eloquent collection with the ->all() method.
This is also working for the other test i had for find($id). That one however didn't require an isEmpty() check so was easier to mock.
I have the following code in one of my routes:
return Response::download('cv.pdf');
Any idea how to test this? I've tried to use shouldReceive() but that doesn't seem to work ('shouldReceive() undefined function....').
$response->assertDownload() was added in Laravel 8.45.0:
Assert that the response is a "download". Typically, this means the invoked route that returned the response returned a Response::download response, BinaryFileResponse, or Storage::download response:
$response->assertDownload();
Learn More:
https://laravel.com/docs/8.x/http-tests#assert-download
EDIT: As pointed by #DavidBarker in his comment to the OP question
The Illuminate\Support\Facades\Response class doesn't actually extend
Illuminate\Support\Facades\Facade so doesnt have the shouldRecieve()
method. You need to test the response of this route after calling it
in a test.
So if you want to test your download functionality, you can try checking the response for errors with:
$this->assertTrue(preg_match('/(error|notice)/i', $response) === false);
You can assert that the status code is 200
$this->assertEquals($response->getStatusCode(), 200);
because sometimes you might have some data returned that match "error" or "notice" and that would be misleading.
I additionally assert that there's an attachment in the response headers:
$this->assertContains('attachment', (string)$response);
You can use Mockery to mock the download method, for this you will need to mock ResponseFactory.
public function testDownloadCsv()
{
$this->instance(
ResponseFactory::class, Mockery::mock(ResponseFactory::class, function ($mock) {
$mock->shouldReceive('download')
->once()
->andReturn(['header' => 'data']);
}));
$response = $this->get('/dowload-csv');
$response->assertStatus(Response::HTTP_OK);
$response->assertJson(['header' => 'data']); // Response
}
The test class below verifies that a simple HttpService gets content from a given URL. Both the implementations shown make the test pass, though one is clearly wrong because it constructs the URL with an incorrect argument.
To avoid this and correctly specify the behaviour I want, I'd like to verify that in the use block of the test case, I construct one (and only one) instance of the URL class, and that the url argument to the constructor is correct. A Groovy enhancement seems like it would let me add the statement
mockURLContext.demand.URL { assertEquals "http://www.foo.com", url }
but what can I do without that Groovy enhancement?
Update: Replaced "mock" with "stub" in the title, as I'm only interested in checking the state not necessarily the detail of the interactions. Groovy has a StubFor mechanism that I haven't used, so I'll leave my code as is, but I think you could just replace MockFor with StubFor throughout.
import grails.test.*
import groovy.mock.interceptor.MockFor
class HttpServiceTests extends GrailsUnitTestCase {
void testGetsContentForURL() {
def content = [text : "<html><body>Hello, world</body></html>"]
def mockURLContext = new MockFor(URL.class)
mockURLContext.demand.getContent { content }
mockURLContext.use {
def httpService = new HttpService()
assertEquals content.text, httpService.getContentFor("http://www.foo.com")
}
}
}
// This is the intended implementation.
class HttpService {
def getContentFor(url) {
new URL(url).content.text
}
}
// This intentionally wrong implementation also passes the test!
class HttpService {
def getContentFor(url) {
new URL("http://www.wrongurl.com").content.text
}
}
What does mocking the URL get you? It makes the test difficult to write. You won't be able to react to feedback the mock objects give you about the design of the API of the URL class, because it's not under your control. And if you don't precisely fake the behaviour of the URL and what it exposes about the HTTP protocol, the test will not be reliable.
You want to test that your "HttpService" object actually loads the data correctly from a given URL, copes with different content type encodings correctly, handles different classes of HTTP status code appropriately, and so forth. When I need to test this kind object -- one that merely wraps some underlying technical infrastructure -- I write a real integration test that verifies that the object really does use the underlying technology correctly.
For HTTP I write a test that creates an HTTP server, plugs a servlet into the server that will return some canned data, passed the URL of the servlet to the object to make it load the data, check that the loaded result is the same as the canned data used to initialise the servlet, and stop the server in the fixture tear-down. I use Jetty or the simple HTTP server that is bundled with JDK 6.
I'd only use mock objects to test the behaviour of objects that talk to the interface(s) of that object I've integration tested.
Putting on my "Programming in the Small" and "Unit test 100%" hat, you could consider this as a single method that does too many things. You could refactor the HttpService to:
class HttpService {
def newURLFrom(urlString) {
new URL(urlString)
}
def getContentText(url) {
url.content.text
}
def getContentFor(urlString) {
getContentText(newURLFrom(urlString))
}
}
This would give you a few more options for testing, as well as split out the factory aspect from the property manipulation. The testing options are bit more mundane then:
class HttpServiceTests extends GroovyTestCase {
def urlString = "http://stackoverflow.com"
def fauxHtml = "<html><body>Hello, world</body></html>";
def fauxURL = [content : [text : fauxHtml]]
void testMakesURLs() {
assertEquals(urlString,
new HTTPService().newURLFrom(urlString).toExternalForm())
}
void testCanDeriveContentText() {
assertEquals(fauxHtml, new HTTPService().getContentText(fauxURL));
}
// Going a bit overboard to test the line combining the two methods
void testGetsContentForURL() {
def service = new HTTPService()
def emc = new ExpandoMetaClass( service.class, false )
emc.newURLFrom = { input -> assertEquals(urlString, input); return fauxURL }
emc.initialize()
service.metaClass = emc
assertEquals(fauxHtml, service.getContentFor(urlString))
}
}
I think that this makes all the assertions that you want, but at the cost of sacrificing test readability in the last case.
I would agree with Nat about this making more sense as an integration test. (You are integrating with Java's URL library on some level.) But assuming that this example simplifies some complex logic, you could use the metaclass to override the instances class effictvely partially mocking the instance.
It's tough to mock out JDK classes that are declared final... Your problem, as you reference through the enhancement, is that there is no way to create a URL other than calling the constructor.
I try to separate the creation of these kinds of objects from the rest of my code; I'd create a factory to separate the creation the URLs. This should be simple enough to not warrant a test. Others take a typical wrapper/decorator approach. Or you may be able to apply the adapter pattern to translate to domain objects that you write.
Here is a similar answer to a surprisingly similar problem: Mocking a URL in Java
I think this demonstrates something that a lot of people learn after doing more testing: the code we write to make things more testable is meant to isolate what we desire to test from what we can safely say is already tested somewhere else. It's a fundamental assumption we have to make in order to do unit testing. It can also provide a decent example of why good unit tests aren't necessarily about 100% code coverage. They have to be economical, too.
Hope this helps.
What exactly are you expecting to have fail? It is not readily apparent what you are trying to test with that code. By Mocking URL.getContent you are telling Groovy to always return the variable content when URL.getContent() is invoked. Are you wishing to make the return value of URL.getContent() conditional based upon the URL string? If that is the case, the following accomplishes that:
import grails.test.*
import groovy.mock.interceptor.MockFor
class HttpServiceTests extends GrailsUnitTestCase {
def connectionUrl
void testGetsContentForURL() {
// put the desired "correct" URL as the key in the next line
def content = ["http://www.foo.com" : "<html><body>Hello, world</body></html>"]
def mockURLContext = new MockFor(URL.class)
mockURLContext.demand.getContent { [text : content[this.connectionUrl]] }
mockURLContext.use {
def httpService = new HttpService()
this.connectionUrl = "http://www.wrongurl.com"
assertEquals content.text, httpService.getContentFor(this.connectionUrl)
}
}
}