Updating a dict with dict values
Tests

It's a good thing you've provided tests and expected output so that one can check the behavior, it is even better if one can run the tests without having to check that the output is correct. Only a tiny bit of reorganisation is required to get :

# my_string not blank, my_key is new
assert my_function('hello', 'test4') == {'test2': {}, 'test3': {'a_key': 'a_value'}, 'test0': {}, 'test1': {}, 'test4': {'hello': None}}

# my_string not blank, my_key exists
assert my_function('hello', 'test0') == {'test2': {}, 'test3': {'a_key': 'a_value'}, 'test0': {'hello': None}, 'test1': {}}

# my_string blank, my_key exists and its value is not empty
assert my_function('', 'test3') == {'test2': {}, 'test3': {'a_key': 'a_value'}, 'test0': {}, 'test1': {}}

# my_string blank, my_key exists and its value is empty
assert my_function('', 'test0') == {'test2': {}, 'test3': {'a_key': 'a_value'}, 'test0': {'NULL': None}, 'test1': {}}

# my_string blank, my_key is new
assert my_function('', 'test4') == {'test2': {}, 'test3': {'a_key': 'a_value'}, 'test0': {}, 'test1': {}, 'test4': {'NULL': None}}

# New test from Sjoerd Job Postmus's comment
assert my_function('something not empty', 'test3') == {'test1': {}, 'test0': {}, 'test3': {'something not empty': None}, 'test2': {}}


Improvements

if my_string != '': could be rewritten more concisely : if my_string:.

Also, you could use get to retrievement elements in a "safe" way.

Thus, the code becomes :

if my_string:
    d[my_key] = {my_string: None}
else:
    if not d.get(my_key):
        d[my_key] = {'NULL': None}
return d


Also, your dict initialisation could be done with a dictionnary comprehension. Then, your whole function would become:

def my_function(my_string, my_key):
    d = {'test' + str(i): {} for i in range(3)}
    d['test3'] = {'a_key': 'a_value'}

    if my_string:
        d[my_key] = {my_string: None}
    elif not d.get(my_key):
        d[my_key] = {'NULL': None}
    return d




Is this as simple as it can be ? Not quite ? The check for d.get(my_key) is actually to distinguish "test3" from the other values. It could be easier to just do the obvious thing instead of trying to retrieve stuff from the dict :

def my_function(my_string, my_key):
    d = {'test' + str(i): {} for i in range(3)}
    d['test3'] = {'a_key': 'a_value'}
    if my_string:
        d[my_key] = {my_string: None}
    elif my_key != 'test3':
        d[my_key] = {'NULL': None}
    return d