Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: Getting NPE when we try to test a client in test double approach with a default parameter value for init function #41330

Open
dulajdilshan opened this issue Sep 4, 2023 · 2 comments
Assignees
Labels
Area/TestFramework Priority/High Team/DevTools Ballerina Developer Tooling ( CLI, Test FW, Package Management, OpenAPI, APIDocs ) Type/Bug

Comments

@dulajdilshan
Copy link
Contributor

dulajdilshan commented Sep 4, 2023

Description

Getting NPE when we try to implement tests for a client in test double approach with default parameter value for inti function

[2023-09-04 10:32:52,424] SEVERE {b7a.log.crash} - Cannot invoke "io.ballerina.runtime.internal.values.FPValue.getFunction()" because "dulaj.x001$test.0.$_init.$MockHttpClient_MockHttpClient_init_url" is null 
java.lang.NullPointerException: Cannot invoke "io.ballerina.runtime.internal.values.FPValue.getFunction()" because "dulaj.x001$test.0.$_init.$MockHttpClient_MockHttpClient_init_url" is null
        at dulaj.x001$test.0.tests.test.initializeHttpClientMock(tests/test.bal:24)
        at dulaj.x001.0.main.initializeHttpClient(main.bal)
        at dulaj.x001.0.$_init.$gen$$0046$0060init$0062(x001:34)
        at dulaj.x001$test.0.$_init.$moduleInit(x001)
        at dulaj.x001$test.0.$_init.$moduleExecute(x001)
        at dulaj.x001$test.0.$_init.$lambda$$moduleExecute$(x001)
        at io.ballerina.runtime.internal.scheduling.SchedulerItem.execute(SchedulerItem.java:54)
        at io.ballerina.runtime.internal.scheduling.Scheduler.run(Scheduler.java:306)
        at io.ballerina.runtime.internal.scheduling.Scheduler.runSafely(Scheduler.java:273)
        at java.base/java.lang.Thread.run(Thread.java:833)

Note: This works fine if we give a value for to the default parameter when we mock the initialization.

Steps to Reproduce

main.bal

import ballerina/http;

type ResponseType record {|
    int userId;
    string title;
    string body;
    int counter;
    string name;
    string username;
    string email;
    int id;
|};

configurable int port = 8345;

configurable string fakeAPIUrl = "https://jsonplaceholder.typicode.com";

function initializeHttpClient(string res) returns http:Client|error => new (string `https://jsonplaceholder.typicode.com"\${res}`);


final http:Client postsApi = check initializeHttpClient("posts");
final http:Client usersApi = check initializeHttpClient("users");

service /jsonph on new http:Listener(port) {
    resource function post dothis(record {|boolean isPosts;|} body) returns ResponseType|error {
        http:Client cl;
        if (body.isPosts) {
            cl = postsApi;
        } else {
            cl = usersApi;
        }
        ResponseType|http:ClientError result = cl->/.post({
            name: "Leanne Graham",
            username: "Bret",
            email: "[email protected]",
            counter: 1,
            title: "Foo",
            body: "foo ",
            userId: 1
        });

        if (result is ResponseType) {
            return result;
        }

        return error("Not found");
    }
}

test.bal

import ballerina/test;
import ballerina/http;

public client class MockHttpClient {
    private final string url;
    isolated function init(string res, string url = "") {
        self.url = url;
    }
    resource function post [string... path](http:RequestMessage message,
            map<string|string[]>? headers = (),
            string? mediaType = (),
            http:TargetType targetType = http:Response,
            *http:QueryParams params) returns http:Response|anydata|http:ClientError {
        return {id: 1, title: "Bar", body: "bar", userId: 1, counter: 1, name: "Json", email: "[email protected]", username: "json"};
    }
}

final http:Client cl = check new (string `http://localhost:${port}/jsonph`);

@test:Mock {
    functionName: "initializeHttpClient"
}
function initializeHttpClientMock(string res) returns http:Client|error =>
    test:mock(http:Client, new MockHttpClient(res));

@test:Config
public function testx002() returns error? {
    ResponseType response = check cl->/dothis.post({
        isPosts: true
    });
    test:assertEquals(response.id, 1, "post.id should be 1");
}
  1. Create a ballerina project and copy the contents here to
  • main.bal
  • tests/test.bal
  1. run bal test

Affected Version(s)

2201.7.2
2201.8.0: https://github.com/ballerina-platform/ballerina-distribution/releases/tag/v2201.8.0-test-pack

OS, DB, other environment details and versions

No response

Related area

-> Runtime

Related issue(s) (optional)

No response

Suggested label(s) (optional)

No response

Suggested assignee(s) (optional)

No response

@ballerina-bot ballerina-bot added the Team/jBallerina All the issues related to BIR, JVM backend code generation and runtime label Sep 4, 2023
@dulajdilshan dulajdilshan changed the title [Bug]: Getting NPE when we try to test a client in test double approach with a default parameter value for inti function [Bug]: Getting NPE when we try to test a client in test double approach with a default parameter value for init function Sep 4, 2023
@HindujaB HindujaB self-assigned this Sep 11, 2023
@HindujaB
Copy link
Contributor

This happens due to the moduleInit execution order for the test module. Currently, we call the generated init of the test module after we call the init of its source module. In this case, the source module init calls the test module client initialization which needs a test module global variables. As the global variables are not initialized yet before the test module init execution, it gives an NPE.

  • init source module
    • Initialize HTTP client
    • Initialize HTTP mock client -> needs default function pointer from the test module
  • init test module
    • Initialize default function pointers

@HindujaB HindujaB assigned Dilhasha and unassigned HindujaB Sep 13, 2023
@HindujaB HindujaB added Area/TestFramework Team/DevTools Ballerina Developer Tooling ( CLI, Test FW, Package Management, OpenAPI, APIDocs ) and removed Team/jBallerina All the issues related to BIR, JVM backend code generation and runtime labels Sep 13, 2023
@Dilhasha
Copy link
Contributor

This happens due to the moduleInit execution order for the test module. Currently, we call the generated init of the test module after we call the init of its source module. In this case, the source module init calls the test module client initialization which needs a test module global variables. As the global variables are not initialized yet before the test module init execution, it gives an NPE.

  • init source module

    • Initialize HTTP client
    • Initialize HTTP mock client -> needs default function pointer from the test module
  • init test module

    • Initialize default function pointers

When we annotate the function with the mock function details, this does compile time mocking. So, the mocking happens before any int phases. https://ballerina.io/learn/test-ballerina-code/test-services-and-clients/#mock-final-clients

The mock seems to be expecting values for parameters with default values as well. If we change the MockHttpClient initialization to pass the url value as well, then the error is resolved.

@test:Mock {
    functionName: "initializeHttpClient"
}
function initializeHttpClientMock(string res) returns http:Client|error =>
    test:mock(http:Client, new MockHttpClient(res, ""));

We will investigate further why the default value is not considered during mocking.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area/TestFramework Priority/High Team/DevTools Ballerina Developer Tooling ( CLI, Test FW, Package Management, OpenAPI, APIDocs ) Type/Bug
Projects
None yet
Development

No branches or pull requests

4 participants